Initial commit of [CQ 5286] Tomcat 7.0.12 Version: Subset (PB CQ5094)
diff --git a/bundles/org.apache.tomcat/.classpath b/bundles/org.apache.tomcat/.classpath
new file mode 100644
index 0000000..ad32c83
--- /dev/null
+++ b/bundles/org.apache.tomcat/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.apache.tomcat/.project b/bundles/org.apache.tomcat/.project
new file mode 100644
index 0000000..99ace6a
--- /dev/null
+++ b/bundles/org.apache.tomcat/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.apache.tomcat</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bundles/org.apache.tomcat/.settings/org.eclipse.jdt.core.prefs b/bundles/org.apache.tomcat/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..b7cbedf
--- /dev/null
+++ b/bundles/org.apache.tomcat/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+#Thu Jun 09 11:01:57 CEST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/bundles/org.apache.tomcat/.settings/org.eclipse.pde.core.prefs b/bundles/org.apache.tomcat/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 0000000..90841bf
--- /dev/null
+++ b/bundles/org.apache.tomcat/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,4 @@
+#Thu Jun 09 11:01:57 CEST 2011
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/bundles/org.apache.tomcat/META-INF/MANIFEST.MF b/bundles/org.apache.tomcat/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..885f7c9
--- /dev/null
+++ b/bundles/org.apache.tomcat/META-INF/MANIFEST.MF
@@ -0,0 +1,27 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Apache Tomcat
+Bundle-SymbolicName: org.apache.tomcat
+Bundle-Version: 7.0.12.qualifier
+Bundle-Vendor: Apache Software Foundation
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Import-Package: javax.servlet;version="3.0.0",
+ javax.servlet.annotation;version="3.0.0",
+ javax.servlet.descriptor;version="3.0.0",
+ javax.servlet.http;version="3.0.0",
+ javax.servlet.resources;version="3.0.0"
+Bundle-ActivationPolicy: lazy
+Export-Package: org.apache.catalina;version="7.0.12.qualifier",
+ org.apache.catalina.connector;version="7.0.12.qualifier",
+ org.apache.catalina.deploy;version="7.0.12.qualifier",
+ org.apache.catalina.ha.session;version="7.0.12.qualifier",
+ org.apache.catalina.ha.tcp;version="7.0.12.qualifier",
+ org.apache.catalina.servlets;version="7.0.12.qualifier",
+ org.apache.catalina.session;version="7.0.12.qualifier",
+ org.apache.catalina.startup;version="7.0.12.qualifier",
+ org.apache.catalina.tribes;version="7.0.12.qualifier",
+ org.apache.catalina.tribes.group;version="7.0.12.qualifier",
+ org.apache.catalina.tribes.group.interceptors;version="7.0.12.qualifier",
+ org.apache.catalina.tribes.membership;version="7.0.12.qualifier",
+ org.apache.catalina.tribes.transport;version="7.0.12.qualifier",
+ org.apache.catalina.tribes.transport.nio;version="7.0.12.qualifier"
diff --git a/bundles/org.apache.tomcat/build.properties b/bundles/org.apache.tomcat/build.properties
new file mode 100644
index 0000000..2d06797
--- /dev/null
+++ b/bundles/org.apache.tomcat/build.properties
@@ -0,0 +1,18 @@
+################################################################################
+# Copyright (c) 2011 EclipseSource and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     EclipseSource - initial API and implementation
+################################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
+javacDefaultEncoding.. = UTF-8
+jre.compilation.profile = JavaSE-1.6
+javacSource = 1.6
+javacTarget = 1.6
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/Generated.java b/bundles/org.apache.tomcat/src/javax/annotation/Generated.java
new file mode 100644
index 0000000..d4721db
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/Generated.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, 
+    ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, 
+    ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+
+public @interface Generated {
+    public String[] value();
+    public String date() default "";
+    public String comment() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/PostConstruct.java b/bundles/org.apache.tomcat/src/javax/annotation/PostConstruct.java
new file mode 100644
index 0000000..8ad363a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/PostConstruct.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface PostConstruct {
+    // No attributes
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/PreDestroy.java b/bundles/org.apache.tomcat/src/javax/annotation/PreDestroy.java
new file mode 100644
index 0000000..d5be75a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/PreDestroy.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface PreDestroy {
+    // No attributes
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/Resource.java b/bundles/org.apache.tomcat/src/javax/annotation/Resource.java
new file mode 100644
index 0000000..21a5d8b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/Resource.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface Resource {
+    public enum AuthenticationType {
+        CONTAINER,
+        APPLICATION
+    }
+    public String name() default "";
+    @SuppressWarnings("rawtypes") // Can't use Class<?> because API needs to match specification
+    public Class type() default Object.class;
+    public AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
+    public boolean shareable() default true;
+    public String description() default "";
+    public String mappedName() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/Resources.java b/bundles/org.apache.tomcat/src/javax/annotation/Resources.java
new file mode 100644
index 0000000..4b398f2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/Resources.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface Resources {
+    public Resource[] value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/security/DeclareRoles.java b/bundles/org.apache.tomcat/src/javax/annotation/security/DeclareRoles.java
new file mode 100644
index 0000000..d5d214a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/security/DeclareRoles.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface DeclareRoles {
+    public String[] value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/security/DenyAll.java b/bundles/org.apache.tomcat/src/javax/annotation/security/DenyAll.java
new file mode 100644
index 0000000..6fdf829
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/security/DenyAll.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface DenyAll {
+    // No attributes
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/security/PermitAll.java b/bundles/org.apache.tomcat/src/javax/annotation/security/PermitAll.java
new file mode 100644
index 0000000..dfe3aa5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/security/PermitAll.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface PermitAll {
+    // No attributes
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/security/RolesAllowed.java b/bundles/org.apache.tomcat/src/javax/annotation/security/RolesAllowed.java
new file mode 100644
index 0000000..fced761
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/security/RolesAllowed.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface RolesAllowed {
+    public String[] value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/annotation/security/RunAs.java b/bundles/org.apache.tomcat/src/javax/annotation/security/RunAs.java
new file mode 100644
index 0000000..3920d7c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/annotation/security/RunAs.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.annotation.security;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface RunAs {
+    public String value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/ejb/EJB.java b/bundles/org.apache.tomcat/src/javax/ejb/EJB.java
new file mode 100644
index 0000000..85233ed
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/ejb/EJB.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.ejb;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface EJB {
+   String name() default "";
+   String description() default "";
+   @SuppressWarnings("rawtypes") // Can't use Class<?> because API needs to match specification
+   Class beanInterface() default java.lang.Object.class;
+   String beanName() default "";
+   String mappedName() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/ejb/EJBs.java b/bundles/org.apache.tomcat/src/javax/ejb/EJBs.java
new file mode 100644
index 0000000..3f752f9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/ejb/EJBs.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.ejb;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface EJBs {
+   EJB[] value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/mail/Authenticator.java b/bundles/org.apache.tomcat/src/javax/mail/Authenticator.java
new file mode 100644
index 0000000..78df841
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/mail/Authenticator.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.mail;
+
+public class Authenticator {
+    protected PasswordAuthentication getPasswordAuthentication() {
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/mail/PasswordAuthentication.java b/bundles/org.apache.tomcat/src/javax/mail/PasswordAuthentication.java
new file mode 100644
index 0000000..d5d9411
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/mail/PasswordAuthentication.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.mail;
+
+@SuppressWarnings("unused") // Dummy implementation
+public class PasswordAuthentication {
+    public PasswordAuthentication(String user, String password) {
+        // Dummy implementation
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/mail/Session.java b/bundles/org.apache.tomcat/src/javax/mail/Session.java
new file mode 100644
index 0000000..a95a11b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/mail/Session.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.mail;
+
+import java.util.Properties;
+
+@SuppressWarnings("unused") // Dummy implementation
+public class Session {
+    public static Session getInstance(Properties props, Authenticator auth) {
+        return null;
+    }
+    public static Session getInstance(Properties props) {
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/HandlesTypes.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/HandlesTypes.java
new file mode 100644
index 0000000..f1ff122
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/HandlesTypes.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to declare an array of application classes which are
+ * passed to a {@link javax.servlet.ServletContainerInitializer}.
+ * 
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@SuppressWarnings("rawtypes") // Spec API does not use generics
+public @interface HandlesTypes {
+
+    /**
+     * @return array of classes
+     */
+    Class[] value();
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/HttpConstraint.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/HttpConstraint.java
new file mode 100644
index 0000000..2223e92
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/HttpConstraint.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+/**
+ * This annotation represents the security constraints that are applied to all
+ * requests with HTTP protocol method types that are not otherwise represented
+ * by a corresponding {@link javax.servlet.annotation.HttpMethodConstraint} in a
+ * {@link javax.servlet.annotation.ServletSecurity} annotation.
+ * 
+ * @since Servlet 3.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface HttpConstraint {
+
+    /**
+     * The EmptyRoleSemantic determines the behaviour when the rolesAllowed list
+     * is empty.
+     * 
+     * @return empty role semantic
+     */
+    EmptyRoleSemantic value() default EmptyRoleSemantic.PERMIT;
+
+    /**
+     * Determines whether SSL/TLS is required to process the current request.
+     * 
+     * @return transport guarantee
+     */
+    TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;
+
+    /**
+     * The authorized roles' names. The container may discard duplicate role
+     * names during processing of the annotation. N.B. The String "*" does not
+     * have a special meaning if it occurs as a role name.
+     * 
+     * @return array of names. The array may be of zero length, in which case
+     *         the EmptyRoleSemantic applies; the returned value determines
+     *         whether access is to be permitted or denied regardless of the
+     *         identity and authentication state in either case, PERMIT or DENY.<br />
+     *         Otherwise, when the array contains one or more role names access
+     *         is permitted if the user a member of at least one of the named
+     *         roles. The EmptyRoleSemantic is not applied in this case.
+     * 
+     */
+    String[] rolesAllowed() default {};
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/HttpMethodConstraint.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/HttpMethodConstraint.java
new file mode 100644
index 0000000..4175a7c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/HttpMethodConstraint.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+/**
+ * Specific security constraints can be applied to different types of request,
+ * differentiated by the HTTP protocol method type by using this annotation
+ * inside the {@link javax.servlet.annotation.ServletSecurity} annotation.
+ * 
+ * @since Servlet 3.0
+ * 
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface HttpMethodConstraint {
+
+    /**
+     * HTTP Protocol method name (e.g. POST, PUT)
+     * 
+     * @return method name
+     */
+    String value();
+
+    /**
+     * The EmptyRoleSemantic determines the behaviour when the rolesAllowed list
+     * is empty.
+     * 
+     * @return empty role semantic
+     */
+    EmptyRoleSemantic emptyRoleSemantic() default EmptyRoleSemantic.PERMIT;
+
+    /**
+     * Determines whether SSL/TLS is required to process the current request.
+     * 
+     * @return transport guarantee
+     */
+    TransportGuarantee transportGuarantee() default TransportGuarantee.NONE;
+
+    /**
+     * The authorized roles' names. The container may discard duplicate role
+     * names during processing of the annotation. N.B. The String "*" does not
+     * have a special meaning if it occurs as a role name.
+     * 
+     * @return array of names. The array may be of zero length, in which case
+     *         the EmptyRoleSemantic applies; the returned value determines
+     *         whether access is to be permitted or denied regardless of the
+     *         identity and authentication state in either case, PERMIT or DENY.<br />
+     *         Otherwise, when the array contains one or more role names access
+     *         is permitted if the user a member of at least one of the named
+     *         roles. The EmptyRoleSemantic is not applied in this case.
+     */
+    String[] rolesAllowed() default {};
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/MultipartConfig.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/MultipartConfig.java
new file mode 100644
index 0000000..4b8bb2f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/MultipartConfig.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to indicate that the {@link javax.servlet.Servlet} on
+ * which it is declared expects requests to made using the {@code
+ * multipart/form-data} MIME type. <br />
+ * <br />
+ * 
+ * {@link javax.servlet.http.Part} components of a given {@code
+ * multipart/form-data} request are retrieved by a Servlet annotated with
+ * {@code MultipartConfig} by calling
+ * {@link javax.servlet.http.HttpServletRequest#getPart} or
+ * {@link javax.servlet.http.HttpServletRequest#getParts}.<br />
+ * <br />
+ * 
+ * E.g. <code>@WebServlet("/upload")}</code><br />
+ * 
+ * <code>@MultipartConfig()</code> <code>public class UploadServlet extends
+ * HttpServlet ... } </code><br />
+ * 
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MultipartConfig {
+
+    /**
+     * @return location in which the Container stores temporary files
+     */
+    String location() default "";
+
+    /**
+     * @return the maximum size allowed for uploaded files (in bytes)
+     */
+    long maxFileSize() default -1L;
+
+    /**
+     * @return the maximum size of the request allowed for {@code
+     *         multipart/form-data}
+     */
+    long maxRequestSize() default -1L;
+
+    /**
+     * @return the size threshold at which the file will be written to the disk
+     */
+    int fileSizeThreshold() default 0;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/ServletSecurity.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/ServletSecurity.java
new file mode 100644
index 0000000..cebb072
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/ServletSecurity.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declare this annotation on a {@link javax.servlet.Servlet} implementation
+ * class to enforce security constraints on HTTP protocol requests.<br />
+ * The container applies constraints to the URL patterns mapped to each Servlet
+ * which declares this annotation.<br />
+ * <br />
+ * 
+ * @since Servlet 3.0
+ */
+@Inherited
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ServletSecurity {
+
+    /**
+     * Represents the two possible values of the empty role semantic, active
+     * when a list of role names is empty.
+     */
+    enum EmptyRoleSemantic {
+
+        /**
+         * Access MUST be permitted, regardless of authentication state or
+         * identity
+         */
+        PERMIT,
+
+        /**
+         * Access MUST be denied, regardless of authentication state or identity
+         */
+        DENY
+    }
+
+    /**
+     * Represents the two possible values of data transport, encrypted or not.
+     */
+    enum TransportGuarantee {
+
+        /**
+         * User data must not be encrypted by the container during transport
+         */
+        NONE,
+
+        /**
+         * The container MUST encrypt user data during transport
+         */
+        CONFIDENTIAL
+    }
+
+    /**
+     * The default constraint to apply to requests not handled by specific
+     * method constraints
+     * 
+     * @return http constraint
+     */
+    HttpConstraint value() default @HttpConstraint;
+
+    /**
+     * An array of HttpMethodContraint objects to which the security constraint
+     * will be applied
+     * 
+     * @return array of http method constraint
+     */
+    HttpMethodConstraint[] httpMethodConstraints() default {};
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebFilter.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebFilter.java
new file mode 100644
index 0000000..aaac994
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebFilter.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.servlet.DispatcherType;
+
+/**
+ * The annotation used to declare a Servlet {@link javax.servlet.Filter}. <br />
+ * <br />
+ * 
+ * This annotation will be processed by the container during deployment, the
+ * Filter class in which it is found will be created as per the configuration
+ * and applied to the URL patterns, {@link javax.servlet.Servlet}s and
+ * {@link javax.servlet.DispatcherType}s.<br />
+ * <br/>
+ * 
+ * If the name attribute is not defined, the fully qualified name of the class
+ * is used.<br/>
+ * <br/>
+ * 
+ * At least one URL pattern MUST be declared in either the {@code value} or
+ * {@code urlPattern} attribute of the annotation, but not both.<br/>
+ * <br/>
+ * 
+ * The {@code value} attribute is recommended for use when the URL pattern is
+ * the only attribute being set, otherwise the {@code urlPattern} attribute
+ * should be used.<br />
+ * <br />
+ * 
+ * The annotated class MUST implement {@link javax.servlet.Filter}.
+ * 
+ * E.g.
+ * 
+ * <code>@WebFilter("/path/*")</code><br />
+ * <code>public class AnExampleFilter implements Filter { ... </code><br />
+ * 
+ * @since Servlet 3.0 (Section 8.1.2)
+ * 
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebFilter {
+
+    /**
+     * @return description of the Filter, if present
+     */
+    String description() default "";
+
+    /**
+     * @return display name of the Filter, if present
+     */
+    String displayName() default "";
+
+    /**
+     * @return array of initialization params for this Filter
+     */
+    WebInitParam[] initParams() default {};
+
+    /**
+     * @return name of the Filter, if present
+     */
+    String filterName() default "";
+
+    /**
+     * @return small icon for this Filter, if present
+     */
+    String smallIcon() default "";
+
+    /**
+     * @return the large icon for this Filter, if present
+     */
+    String largeIcon() default "";
+
+    /**
+     * @return array of Servlet names to which this Filter applies
+     */
+    String[] servletNames() default {};
+
+    /**
+     * A convenience method, to allow extremely simple annotation of a class.
+     * 
+     * @return array of URL patterns
+     * @see #urlPatterns()
+     */
+    String[] value() default {};
+
+    /**
+     * @return array of URL patterns to which this Filter applies
+     */
+    String[] urlPatterns() default {};
+
+    /**
+     * @return array of DispatcherTypes to which this filter applies
+     */
+    DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};
+
+    /**
+     * @return asynchronous operation supported by this Filter
+     */
+    boolean asyncSupported() default false;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebInitParam.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebInitParam.java
new file mode 100644
index 0000000..d4b7573
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebInitParam.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotation used to declare an initialization parameter on a
+ * {@link javax.servlet.Servlet} or {@link javax.servlet.Filter}, within a
+ * {@link javax.servlet.annotation.WebFilter} or
+ * {@link javax.servlet.annotation.WebServlet} annotation.<br />
+ * <br />
+ * 
+ * E.g.
+ * <code>&amp;#064;WebServlet(name="TestServlet", urlPatterns={"/test"},initParams={&amp;#064;WebInitParam(name="test", value="true")})
+ * public class TestServlet extends HttpServlet { ... </code><br />
+ * 
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebInitParam {
+
+    /**
+     * @return name of the initialization parameter
+     */
+    String name();
+
+    /**
+     * @return value of the initialization parameter
+     */
+    String value();
+
+    /**
+     * @return description of the initialization parameter
+     */
+    String description() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebListener.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebListener.java
new file mode 100644
index 0000000..eeed16d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebListener.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The annotation used to declare a listener for various types of event, in a
+ * given web application context.<br />
+ * <br />
+ * 
+ * The class annotated MUST implement one, (or more), of the following
+ * interfaces: {@link javax.servlet.http.HttpSessionAttributeListener},
+ * {@link javax.servlet.http.HttpSessionListener},
+ * {@link javax.servlet.ServletContextAttributeListener},
+ * {@link javax.servlet.ServletContextListener},
+ * {@link javax.servlet.ServletRequestAttributeListener},
+ * {@link javax.servlet.ServletRequestListener} <br />
+ * <br />
+ * 
+ * E.g. <code>@WebListener</code><br />
+ * <code>public TestListener implements ServletContextListener {</code><br />
+ * 
+ * @since Servlet 3.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebListener {
+
+    /**
+     * @return description of the listener, if present
+     */
+    String value() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebServlet.java b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebServlet.java
new file mode 100644
index 0000000..53f9b4c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/annotation/WebServlet.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to declare the configuration of an
+ * {@link javax.servlet.Servlet}. <br/>
+ * 
+ * If the name attribute is not defined, the fully qualified name of the class
+ * is used.<br/>
+ * <br/>
+ * 
+ * At least one URL pattern MUST be declared in either the {@code value} or
+ * {@code urlPattern} attribute of the annotation, but not both.<br/>
+ * <br/>
+ * 
+ * The {@code value} attribute is recommended for use when the URL pattern is
+ * the only attribute being set, otherwise the {@code urlPattern} attribute
+ * should be used.<br />
+ * <br />
+ * 
+ * The class on which this annotation is declared MUST extend
+ * {@link javax.servlet.http.HttpServlet}. <br />
+ * <br />
+ * 
+ * E.g. <code>@WebServlet("/path")}<br />
+ * public class TestServlet extends HttpServlet ... {</code><br />
+ * 
+ * E.g.
+ * <code>@WebServlet(name="TestServlet", urlPatterns={"/path", "/alt"}) <br />
+ * public class TestServlet extends HttpServlet ... {</code><br />
+ * 
+ * @since Servlet 3.0 (Section 8.1.1)
+ * 
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface WebServlet {
+
+    /**
+     * @return name of the Servlet
+     */
+    String name() default "";
+
+    /**
+     * A convenience method, to allow extremely simple annotation of a class.
+     * 
+     * @return array of URL patterns
+     * @see #urlPatterns()
+     */
+    String[] value() default {};
+
+    /**
+     * @return array of URL patterns to which this Filter applies
+     */
+    String[] urlPatterns() default {};
+
+    /**
+     * @return load on startup ordering hint
+     */
+    int loadOnStartup() default -1;
+
+    /**
+     * @return array of initialization params for this Servlet
+     */
+    WebInitParam[] initParams() default {};
+
+    /**
+     * @return asynchronous operation supported by this Servlet
+     */
+    boolean asyncSupported() default false;
+
+    /**
+     * @return small icon for this Servlet, if present
+     */
+    String smallIcon() default "";
+
+    /**
+     * @return large icon for this Servlet, if present
+     */
+    String largeIcon() default "";
+
+    /**
+     * @return description of this Servlet, if present
+     */
+    String description() default "";
+
+    /**
+     * @return display name of this Servlet, if present
+     */
+    String displayName() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/descriptor/JspConfigDescriptor.java b/bundles/org.apache.tomcat/src/javax/servlet/descriptor/JspConfigDescriptor.java
new file mode 100644
index 0000000..8296d44
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/descriptor/JspConfigDescriptor.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.descriptor;
+
+import java.util.Collection;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface JspConfigDescriptor {
+    public Collection<TaglibDescriptor> getTaglibs();
+    public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/descriptor/JspPropertyGroupDescriptor.java b/bundles/org.apache.tomcat/src/javax/servlet/descriptor/JspPropertyGroupDescriptor.java
new file mode 100644
index 0000000..25c71a9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/descriptor/JspPropertyGroupDescriptor.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.descriptor;
+
+import java.util.Collection;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface JspPropertyGroupDescriptor {
+    public Collection<String> getUrlPatterns();
+    public String getElIgnored();
+    public String getPageEncoding();
+    public String getScriptingInvalid();
+    public String getIsXml();
+    public Collection<String> getIncludePreludes();
+    public Collection<String> getIncludeCodas();
+    public String getDeferredSyntaxAllowedAsLiteral();
+    public String getTrimDirectiveWhitespaces();
+    public String getDefaultContentType();
+    public String getBuffer();
+    public String getErrorOnUndeclaredNamespace();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/descriptor/TaglibDescriptor.java b/bundles/org.apache.tomcat/src/javax/servlet/descriptor/TaglibDescriptor.java
new file mode 100644
index 0000000..f930c5d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/descriptor/TaglibDescriptor.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.descriptor;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface TaglibDescriptor {
+    public String getTaglibURI();
+    public String getTaglibLocation();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/Cookie.java b/bundles/org.apache.tomcat/src/javax/servlet/http/Cookie.java
new file mode 100644
index 0000000..829b242
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/Cookie.java
@@ -0,0 +1,468 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * Creates a cookie, a small amount of information sent by a servlet to a Web
+ * browser, saved by the browser, and later sent back to the server. A cookie's
+ * value can uniquely identify a client, so cookies are commonly used for
+ * session management.
+ * <p>
+ * A cookie has a name, a single value, and optional attributes such as a
+ * comment, path and domain qualifiers, a maximum age, and a version number.
+ * Some Web browsers have bugs in how they handle the optional attributes, so
+ * use them sparingly to improve the interoperability of your servlets.
+ * <p>
+ * The servlet sends cookies to the browser by using the
+ * {@link HttpServletResponse#addCookie} method, which adds fields to HTTP
+ * response headers to send cookies to the browser, one at a time. The browser
+ * is expected to support 20 cookies for each Web server, 300 cookies total, and
+ * may limit cookie size to 4 KB each.
+ * <p>
+ * The browser returns cookies to the servlet by adding fields to HTTP request
+ * headers. Cookies can be retrieved from a request by using the
+ * {@link HttpServletRequest#getCookies} method. Several cookies might have the
+ * same name but different path attributes.
+ * <p>
+ * Cookies affect the caching of the Web pages that use them. HTTP 1.0 does not
+ * cache pages that use cookies created with this class. This class does not
+ * support the cache control defined with HTTP 1.1.
+ * <p>
+ * This class supports both the Version 0 (by Netscape) and Version 1 (by RFC
+ * 2109) cookie specifications. By default, cookies are created using Version 0
+ * to ensure the best interoperability.
+ * 
+ * @author Various
+ * @version $Version$
+ */
+public class Cookie implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
+    private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
+
+    //
+    // The value of the cookie itself.
+    //
+
+    private String name; // NAME= ... "$Name" style is reserved
+    private String value; // value of NAME
+
+    //
+    // Attributes encoded in the header's cookie fields.
+    //
+
+    private String comment; // ;Comment=VALUE ... describes cookie's use
+    // ;Discard ... implied by maxAge < 0
+    private String domain; // ;Domain=VALUE ... domain that sees cookie
+    private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
+    private String path; // ;Path=VALUE ... URLs that see the cookie
+    private boolean secure; // ;Secure ... e.g. use SSL
+    private int version = 0; // ;Version=1 ... means RFC 2109++ style
+    private boolean httpOnly; // Not in cookie specs, but supported by browsers
+
+    /**
+     * Constructs a cookie with a specified name and value.
+     * <p>
+     * The name must conform to RFC 2109. That means it can contain only ASCII
+     * alphanumeric characters and cannot contain commas, semicolons, or white
+     * space or begin with a $ character. The cookie's name cannot be changed
+     * after creation.
+     * <p>
+     * The value can be anything the server chooses to send. Its value is
+     * probably of interest only to the server. The cookie's value can be
+     * changed after creation with the <code>setValue</code> method.
+     * <p>
+     * By default, cookies are created according to the Netscape cookie
+     * specification. The version can be changed with the
+     * <code>setVersion</code> method.
+     * 
+     * @param name
+     *            a <code>String</code> specifying the name of the cookie
+     * @param value
+     *            a <code>String</code> specifying the value of the cookie
+     * @throws IllegalArgumentException
+     *             if the cookie name contains illegal characters (for example,
+     *             a comma, space, or semicolon) or it is one of the tokens
+     *             reserved for use by the cookie protocol
+     * @see #setValue
+     * @see #setVersion
+     */
+    public Cookie(String name, String value) {
+        if (name == null || name.length() == 0) {
+            throw new IllegalArgumentException(
+                    lStrings.getString("err.cookie_name_blank"));
+        }
+        if (!isToken(name) ||
+                name.equalsIgnoreCase("Comment") // rfc2019
+                ||
+                name.equalsIgnoreCase("Discard") // 2019++
+                ||
+                name.equalsIgnoreCase("Domain") ||
+                name.equalsIgnoreCase("Expires") // (old cookies)
+                ||
+                name.equalsIgnoreCase("Max-Age") // rfc2019
+                || name.equalsIgnoreCase("Path") ||
+                name.equalsIgnoreCase("Secure") ||
+                name.equalsIgnoreCase("Version") || name.startsWith("$")) {
+            String errMsg = lStrings.getString("err.cookie_name_is_token");
+            Object[] errArgs = new Object[1];
+            errArgs[0] = name;
+            errMsg = MessageFormat.format(errMsg, errArgs);
+            throw new IllegalArgumentException(errMsg);
+        }
+
+        this.name = name;
+        this.value = value;
+    }
+
+    /**
+     * Specifies a comment that describes a cookie's purpose. The comment is
+     * useful if the browser presents the cookie to the user. Comments are not
+     * supported by Netscape Version 0 cookies.
+     * 
+     * @param purpose
+     *            a <code>String</code> specifying the comment to display to the
+     *            user
+     * @see #getComment
+     */
+    public void setComment(String purpose) {
+        comment = purpose;
+    }
+
+    /**
+     * Returns the comment describing the purpose of this cookie, or
+     * <code>null</code> if the cookie has no comment.
+     * 
+     * @return a <code>String</code> containing the comment, or
+     *         <code>null</code> if none
+     * @see #setComment
+     */
+    public String getComment() {
+        return comment;
+    }
+
+    /**
+     * Specifies the domain within which this cookie should be presented.
+     * <p>
+     * The form of the domain name is specified by RFC 2109. A domain name
+     * begins with a dot (<code>.foo.com</code>) and means that the cookie is
+     * visible to servers in a specified Domain Name System (DNS) zone (for
+     * example, <code>www.foo.com</code>, but not <code>a.b.foo.com</code>). By
+     * default, cookies are only returned to the server that sent them.
+     * 
+     * @param pattern
+     *            a <code>String</code> containing the domain name within which
+     *            this cookie is visible; form is according to RFC 2109
+     * @see #getDomain
+     */
+    public void setDomain(String pattern) {
+        domain = pattern.toLowerCase(Locale.ENGLISH); // IE allegedly needs this
+    }
+
+    /**
+     * Returns the domain name set for this cookie. The form of the domain name
+     * is set by RFC 2109.
+     * 
+     * @return a <code>String</code> containing the domain name
+     * @see #setDomain
+     */
+    public String getDomain() {
+        return domain;
+    }
+
+    /**
+     * Sets the maximum age of the cookie in seconds.
+     * <p>
+     * A positive value indicates that the cookie will expire after that many
+     * seconds have passed. Note that the value is the <i>maximum</i> age when
+     * the cookie will expire, not the cookie's current age.
+     * <p>
+     * A negative value means that the cookie is not stored persistently and
+     * will be deleted when the Web browser exits. A zero value causes the
+     * cookie to be deleted.
+     * 
+     * @param expiry
+     *            an integer specifying the maximum age of the cookie in
+     *            seconds; if negative, means the cookie is not stored; if zero,
+     *            deletes the cookie
+     * @see #getMaxAge
+     */
+    public void setMaxAge(int expiry) {
+        maxAge = expiry;
+    }
+
+    /**
+     * Returns the maximum age of the cookie, specified in seconds, By default,
+     * <code>-1</code> indicating the cookie will persist until browser
+     * shutdown.
+     * 
+     * @return an integer specifying the maximum age of the cookie in seconds; if
+     *         negative, means the cookie persists until browser shutdown
+     * @see #setMaxAge
+     */
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    /**
+     * Specifies a path for the cookie to which the client should return the
+     * cookie.
+     * <p>
+     * The cookie is visible to all the pages in the directory you specify, and
+     * all the pages in that directory's subdirectories. A cookie's path must
+     * include the servlet that set the cookie, for example, <i>/catalog</i>,
+     * which makes the cookie visible to all directories on the server under
+     * <i>/catalog</i>.
+     * <p>
+     * Consult RFC 2109 (available on the Internet) for more information on
+     * setting path names for cookies.
+     * 
+     * @param uri
+     *            a <code>String</code> specifying a path
+     * @see #getPath
+     */
+    public void setPath(String uri) {
+        path = uri;
+    }
+
+    /**
+     * Returns the path on the server to which the browser returns this cookie.
+     * The cookie is visible to all subpaths on the server.
+     * 
+     * @return a <code>String</code> specifying a path that contains a servlet
+     *         name, for example, <i>/catalog</i>
+     * @see #setPath
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Indicates to the browser whether the cookie should only be sent using a
+     * secure protocol, such as HTTPS or SSL.
+     * <p>
+     * The default value is <code>false</code>.
+     * 
+     * @param flag
+     *            if <code>true</code>, sends the cookie from the browser to the
+     *            server only when using a secure protocol; if
+     *            <code>false</code>, sent on any protocol
+     * @see #getSecure
+     */
+    public void setSecure(boolean flag) {
+        secure = flag;
+    }
+
+    /**
+     * Returns <code>true</code> if the browser is sending cookies only over a
+     * secure protocol, or <code>false</code> if the browser can send cookies
+     * using any protocol.
+     * 
+     * @return <code>true</code> if the browser uses a secure protocol;
+     *         otherwise, <code>true</code>
+     * @see #setSecure
+     */
+    public boolean getSecure() {
+        return secure;
+    }
+
+    /**
+     * Returns the name of the cookie. The name cannot be changed after
+     * creation.
+     * 
+     * @return a <code>String</code> specifying the cookie's name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Assigns a new value to a cookie after the cookie is created. If you use a
+     * binary value, you may want to use BASE64 encoding.
+     * <p>
+     * With Version 0 cookies, values should not contain white space, brackets,
+     * parentheses, equals signs, commas, double quotes, slashes, question
+     * marks, at signs, colons, and semicolons. Empty values may not behave the
+     * same way on all browsers.
+     * 
+     * @param newValue
+     *            a <code>String</code> specifying the new value
+     * @see #getValue
+     * @see Cookie
+     */
+    public void setValue(String newValue) {
+        value = newValue;
+    }
+
+    /**
+     * Returns the value of the cookie.
+     * 
+     * @return a <code>String</code> containing the cookie's present value
+     * @see #setValue
+     * @see Cookie
+     */
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * Returns the version of the protocol this cookie complies with. Version 1
+     * complies with RFC 2109, and version 0 complies with the original cookie
+     * specification drafted by Netscape. Cookies provided by a browser use and
+     * identify the browser's cookie version.
+     * 
+     * @return 0 if the cookie complies with the original Netscape specification;
+     *         1 if the cookie complies with RFC 2109
+     * @see #setVersion
+     */
+    public int getVersion() {
+        return version;
+    }
+
+    /**
+     * Sets the version of the cookie protocol this cookie complies with.
+     * Version 0 complies with the original Netscape cookie specification.
+     * Version 1 complies with RFC 2109.
+     * <p>
+     * Since RFC 2109 is still somewhat new, consider version 1 as experimental;
+     * do not use it yet on production sites.
+     * 
+     * @param v
+     *            0 if the cookie should comply with the original Netscape
+     *            specification; 1 if the cookie should comply with RFC 2109
+     * @see #getVersion
+     */
+    public void setVersion(int v) {
+        version = v;
+    }
+
+    // Note -- disabled for now to allow full Netscape compatibility
+    // from RFC 2068, token special case characters
+    // 
+    // private static final String tspecials = "()<>@,;:\\\"/[]?={} \t";
+
+    private static final String tspecials = ",; ";
+    private static final String tspecials2NoSlash = "()<>@,;:\\\"[]?={} \t";
+    private static final String tspecials2WithSlash = tspecials2NoSlash + "/";
+    private static final String tspecials2;
+
+    /**
+     * If set to true, we parse cookies strictly according to the servlet,
+     * cookie and HTTP specs by default.
+     */
+    private static final boolean STRICT_SERVLET_COMPLIANCE;
+
+    /**
+     * If set to true, the <code>/</code> character will be treated as a
+     * separator. Default is usually false. If STRICT_SERVLET_COMPLIANCE==true
+     * then default is true. Explicitly setting always takes priority.
+     */
+    private static final boolean FWD_SLASH_IS_SEPARATOR;
+
+    /**
+     * If set to true, enforce the cookie naming rules in the spec that require
+     * no separators in the cookie name. Default is usually false. If
+     * STRICT_SERVLET_COMPLIANCE==true then default is true. Explicitly setting
+     * always takes priority.
+     */
+    private static final boolean STRICT_NAMING;
+
+    static {
+        STRICT_SERVLET_COMPLIANCE = Boolean.valueOf(
+                System.getProperty(
+                        "org.apache.catalina.STRICT_SERVLET_COMPLIANCE",
+                        "false")).booleanValue();
+
+        String fwdSlashIsSeparator = System.getProperty("org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
+        if (fwdSlashIsSeparator == null) {
+            FWD_SLASH_IS_SEPARATOR = STRICT_SERVLET_COMPLIANCE;
+        } else {
+            FWD_SLASH_IS_SEPARATOR = Boolean.valueOf(fwdSlashIsSeparator).booleanValue();
+        }
+
+        if (FWD_SLASH_IS_SEPARATOR) {
+            tspecials2 = tspecials2WithSlash;
+        } else {
+            tspecials2 = tspecials2NoSlash;
+        }
+
+        String strictNaming = System.getProperty("org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING");
+        if (strictNaming == null) {
+            STRICT_NAMING = STRICT_SERVLET_COMPLIANCE;
+        } else {
+            STRICT_NAMING = Boolean.valueOf(strictNaming).booleanValue();
+        }
+
+    }
+
+    /*
+     * Tests a string and returns true if the string counts as a reserved token
+     * in the Java language.
+     * @param value the <code>String</code> to be tested
+     * @return <code>true</code> if the <code>String</code> is a reserved token;
+     * <code>false</code> if it is not
+     */
+    private boolean isToken(String possibleToken) {
+        int len = possibleToken.length();
+
+        for (int i = 0; i < len; i++) {
+            char c = possibleToken.charAt(i);
+
+            if (c < 0x20 || c >= 0x7f || tspecials.indexOf(c) != -1 ||
+                    (STRICT_NAMING && tspecials2.indexOf(c) != -1)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Overrides the standard <code>java.lang.Object.clone</code> method to
+     * return a copy of this cookie.
+     */
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    /**
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean isHttpOnly() {
+        return httpOnly;
+    }
+
+    /**
+     * @param httpOnly
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public void setHttpOnly(boolean httpOnly) {
+        this.httpOnly = httpOnly;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServlet.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServlet.java
new file mode 100644
index 0000000..92ea07d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServlet.java
@@ -0,0 +1,822 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * Provides an abstract class to be subclassed to create
+ * an HTTP servlet suitable for a Web site. A subclass of
+ * <code>HttpServlet</code> must override at least 
+ * one method, usually one of these:
+ *
+ * <ul>
+ * <li> <code>doGet</code>, if the servlet supports HTTP GET requests
+ * <li> <code>doPost</code>, for HTTP POST requests
+ * <li> <code>doPut</code>, for HTTP PUT requests
+ * <li> <code>doDelete</code>, for HTTP DELETE requests
+ * <li> <code>init</code> and <code>destroy</code>, 
+ * to manage resources that are held for the life of the servlet
+ * <li> <code>getServletInfo</code>, which the servlet uses to
+ * provide information about itself 
+ * </ul>
+ *
+ * <p>There's almost no reason to override the <code>service</code>
+ * method. <code>service</code> handles standard HTTP
+ * requests by dispatching them to the handler methods
+ * for each HTTP request type (the <code>do</code><i>XXX</i>
+ * methods listed above).
+ *
+ * <p>Likewise, there's almost no reason to override the 
+ * <code>doOptions</code> and <code>doTrace</code> methods.
+ * 
+ * <p>Servlets typically run on multithreaded servers,
+ * so be aware that a servlet must handle concurrent
+ * requests and be careful to synchronize access to shared resources.
+ * Shared resources include in-memory data such as
+ * instance or class variables and external objects
+ * such as files, database connections, and network 
+ * connections.
+ * See the
+ * <a href="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
+ * Java Tutorial on Multithreaded Programming</a> for more
+ * information on handling multiple threads in a Java program.
+ *
+ * @author  Various
+ * @version $Version$
+ */
+public abstract class HttpServlet extends GenericServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String METHOD_DELETE = "DELETE";
+    private static final String METHOD_HEAD = "HEAD";
+    private static final String METHOD_GET = "GET";
+    private static final String METHOD_OPTIONS = "OPTIONS";
+    private static final String METHOD_POST = "POST";
+    private static final String METHOD_PUT = "PUT";
+    private static final String METHOD_TRACE = "TRACE";
+
+    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
+    private static final String HEADER_LASTMOD = "Last-Modified";
+    
+    private static final String LSTRING_FILE =
+        "javax.servlet.http.LocalStrings";
+    private static ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+   
+    
+    /**
+     * Does nothing, because this is an abstract class.
+     */
+    public HttpServlet() {
+        // NOOP
+    }
+    
+    
+    /**
+     * Called by the server (via the <code>service</code> method) to
+     * allow a servlet to handle a GET request. 
+     *
+     * <p>Overriding this method to support a GET request also
+     * automatically supports an HTTP HEAD request. A HEAD
+     * request is a GET request that returns no body in the
+     * response, only the request header fields.
+     *
+     * <p>When overriding this method, read the request data,
+     * write the response headers, get the response's writer or 
+     * output stream object, and finally, write the response data.
+     * It's best to include content type and encoding. When using
+     * a <code>PrintWriter</code> object to return the response,
+     * set the content type before accessing the
+     * <code>PrintWriter</code> object.
+     *
+     * <p>The servlet container must write the headers before
+     * committing the response, because in HTTP the headers must be sent
+     * before the response body.
+     *
+     * <p>Where possible, set the Content-Length header (with the
+     * {@link javax.servlet.ServletResponse#setContentLength} method),
+     * to allow the servlet container to use a persistent connection 
+     * to return its response to the client, improving performance.
+     * The content length is automatically set if the entire response fits
+     * inside the response buffer.
+     *
+     * <p>When using HTTP 1.1 chunked encoding (which means that the response
+     * has a Transfer-Encoding header), do not set the Content-Length header.
+     *
+     * <p>The GET method should be safe, that is, without
+     * any side effects for which users are held responsible.
+     * For example, most form queries have no side effects.
+     * If a client request is intended to change stored data,
+     * the request should use some other HTTP method.
+     *
+     * <p>The GET method should also be idempotent, meaning
+     * that it can be safely repeated. Sometimes making a
+     * method safe also makes it idempotent. For example, 
+     * repeating queries is both safe and idempotent, but
+     * buying a product online or modifying data is neither
+     * safe nor idempotent. 
+     *
+     * <p>If the request is incorrectly formatted, <code>doGet</code>
+     * returns an HTTP "Bad Request" message.
+     *
+     * @param req   an {@link HttpServletRequest} object that
+     *                  contains the request the client has made
+     *                  of the servlet
+     *
+     * @param resp  an {@link HttpServletResponse} object that
+     *                  contains the response the servlet sends
+     *                  to the client
+     * 
+     * @exception IOException   if an input or output error is 
+     *                              detected when the servlet handles
+     *                              the GET request
+     *
+     * @exception ServletException  if the request for the GET
+     *                                  could not be handled
+     * 
+     * @see javax.servlet.ServletResponse#setContentType
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException
+    {
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_get_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+
+
+    /**
+     * Returns the time the <code>HttpServletRequest</code>
+     * object was last modified,
+     * in milliseconds since midnight January 1, 1970 GMT.
+     * If the time is unknown, this method returns a negative
+     * number (the default).
+     *
+     * <p>Servlets that support HTTP GET requests and can quickly determine
+     * their last modification time should override this method.
+     * This makes browser and proxy caches work more effectively,
+     * reducing the load on server and network resources.
+     *
+     * @param req   the <code>HttpServletRequest</code> 
+     *                  object that is sent to the servlet
+     *
+     * @return  a <code>long</code> integer specifying
+     *              the time the <code>HttpServletRequest</code>
+     *              object was last modified, in milliseconds
+     *              since midnight, January 1, 1970 GMT, or
+     *              -1 if the time is not known
+     */
+    protected long getLastModified(HttpServletRequest req) {
+        return -1;
+    }
+
+
+    /**
+     * <p>Receives an HTTP HEAD request from the protected
+     * <code>service</code> method and handles the
+     * request.
+     * The client sends a HEAD request when it wants
+     * to see only the headers of a response, such as
+     * Content-Type or Content-Length. The HTTP HEAD
+     * method counts the output bytes in the response
+     * to set the Content-Length header accurately.
+     *
+     * <p>If you override this method, you can avoid computing
+     * the response body and just set the response headers
+     * directly to improve performance. Make sure that the
+     * <code>doHead</code> method you write is both safe
+     * and idempotent (that is, protects itself from being
+     * called multiple times for one HTTP HEAD request).
+     *
+     * <p>If the HTTP HEAD request is incorrectly formatted,
+     * <code>doHead</code> returns an HTTP "Bad Request"
+     * message.
+     *
+     * @param req   the request object that is passed to the servlet
+     *
+     * @param resp  the response object that the servlet
+     *                  uses to return the headers to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *
+     * @exception ServletException  if the request for the HEAD
+     *                                  could not be handled
+     */
+    protected void doHead(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        NoBodyResponse response = new NoBodyResponse(resp);
+
+        doGet(req, response);
+        response.setContentLength();
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a POST request.
+     *
+     * The HTTP POST method allows the client to send
+     * data of unlimited length to the Web server a single time
+     * and is useful when posting information such as
+     * credit card numbers.
+     *
+     * <p>When overriding this method, read the request data,
+     * write the response headers, get the response's writer or output
+     * stream object, and finally, write the response data. It's best 
+     * to include content type and encoding. When using a
+     * <code>PrintWriter</code> object to return the response, set the 
+     * content type before accessing the <code>PrintWriter</code> object. 
+     *
+     * <p>The servlet container must write the headers before committing the
+     * response, because in HTTP the headers must be sent before the 
+     * response body.
+     *
+     * <p>Where possible, set the Content-Length header (with the
+     * {@link javax.servlet.ServletResponse#setContentLength} method),
+     * to allow the servlet container to use a persistent connection 
+     * to return its response to the client, improving performance.
+     * The content length is automatically set if the entire response fits
+     * inside the response buffer.  
+     *
+     * <p>When using HTTP 1.1 chunked encoding (which means that the response
+     * has a Transfer-Encoding header), do not set the Content-Length header. 
+     *
+     * <p>This method does not need to be either safe or idempotent.
+     * Operations requested through POST can have side effects for
+     * which the user can be held accountable, for example, 
+     * updating stored data or buying items online.
+     *
+     * <p>If the HTTP POST request is incorrectly formatted,
+     * <code>doPost</code> returns an HTTP "Bad Request" message.
+     *
+     *
+     * @param req   an {@link HttpServletRequest} object that
+     *                  contains the request the client has made
+     *                  of the servlet
+     *
+     * @param resp  an {@link HttpServletResponse} object that
+     *                  contains the response the servlet sends
+     *                  to the client
+     * 
+     * @exception IOException   if an input or output error is 
+     *                              detected when the servlet handles
+     *                              the request
+     *
+     * @exception ServletException  if the request for the POST
+     *                                  could not be handled
+     *
+     * @see javax.servlet.ServletOutputStream
+     * @see javax.servlet.ServletResponse#setContentType
+     */
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_post_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a PUT request.
+     *
+     * The PUT operation allows a client to 
+     * place a file on the server and is similar to 
+     * sending a file by FTP.
+     *
+     * <p>When overriding this method, leave intact
+     * any content headers sent with the request (including
+     * Content-Length, Content-Type, Content-Transfer-Encoding,
+     * Content-Encoding, Content-Base, Content-Language, Content-Location,
+     * Content-MD5, and Content-Range). If your method cannot
+     * handle a content header, it must issue an error message
+     * (HTTP 501 - Not Implemented) and discard the request.
+     * For more information on HTTP 1.1, see RFC 2616
+     * <a href="http://www.ietf.org/rfc/rfc2616.txt"></a>.
+     *
+     * <p>This method does not need to be either safe or idempotent.
+     * Operations that <code>doPut</code> performs can have side
+     * effects for which the user can be held accountable. When using
+     * this method, it may be useful to save a copy of the
+     * affected URL in temporary storage.
+     *
+     * <p>If the HTTP PUT request is incorrectly formatted,
+     * <code>doPut</code> returns an HTTP "Bad Request" message.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              PUT request
+     *
+     * @exception ServletException  if the request for the PUT
+     *                                  cannot be handled
+     */
+    protected void doPut(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_put_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a DELETE request.
+     *
+     * The DELETE operation allows a client to remove a document
+     * or Web page from the server.
+     * 
+     * <p>This method does not need to be either safe
+     * or idempotent. Operations requested through
+     * DELETE can have side effects for which users
+     * can be held accountable. When using
+     * this method, it may be useful to save a copy of the
+     * affected URL in temporary storage.
+     *
+     * <p>If the HTTP DELETE request is incorrectly formatted,
+     * <code>doDelete</code> returns an HTTP "Bad Request"
+     * message.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client                                
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              DELETE request
+     *
+     * @exception ServletException  if the request for the
+     *                                  DELETE cannot be handled
+     */
+    protected void doDelete(HttpServletRequest req,
+                            HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String protocol = req.getProtocol();
+        String msg = lStrings.getString("http.method_delete_not_supported");
+        if (protocol.endsWith("1.1")) {
+            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+        } else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+        }
+    }
+    
+
+    private static Method[] getAllDeclaredMethods(Class<?> c) {
+
+        if (c.equals(javax.servlet.http.HttpServlet.class)) {
+            return null;
+        }
+
+        Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
+        Method[] thisMethods = c.getDeclaredMethods();
+        
+        if ((parentMethods != null) && (parentMethods.length > 0)) {
+            Method[] allMethods =
+                new Method[parentMethods.length + thisMethods.length];
+            System.arraycopy(parentMethods, 0, allMethods, 0,
+                             parentMethods.length);
+            System.arraycopy(thisMethods, 0, allMethods, parentMethods.length,
+                             thisMethods.length);
+
+            thisMethods = allMethods;
+        }
+
+        return thisMethods;
+    }
+
+
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a OPTIONS request.
+     *
+     * The OPTIONS request determines which HTTP methods 
+     * the server supports and
+     * returns an appropriate header. For example, if a servlet
+     * overrides <code>doGet</code>, this method returns the
+     * following header:
+     *
+     * <p><code>Allow: GET, HEAD, TRACE, OPTIONS</code>
+     *
+     * <p>There's no need to override this method unless the
+     * servlet implements new HTTP methods, beyond those 
+     * implemented by HTTP 1.1.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client                                
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              OPTIONS request
+     *
+     * @exception ServletException  if the request for the
+     *                                  OPTIONS cannot be handled
+     */
+    protected void doOptions(HttpServletRequest req,
+            HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        Method[] methods = getAllDeclaredMethods(this.getClass());
+        
+        boolean ALLOW_GET = false;
+        boolean ALLOW_HEAD = false;
+        boolean ALLOW_POST = false;
+        boolean ALLOW_PUT = false;
+        boolean ALLOW_DELETE = false;
+        boolean ALLOW_TRACE = true;
+        boolean ALLOW_OPTIONS = true;
+        
+        for (int i=0; i<methods.length; i++) {
+            Method m = methods[i];
+            
+            if (m.getName().equals("doGet")) {
+                ALLOW_GET = true;
+                ALLOW_HEAD = true;
+            }
+            if (m.getName().equals("doPost")) 
+                ALLOW_POST = true;
+            if (m.getName().equals("doPut"))
+                ALLOW_PUT = true;
+            if (m.getName().equals("doDelete"))
+                ALLOW_DELETE = true;
+        }
+        
+        String allow = null;
+        if (ALLOW_GET)
+            allow=METHOD_GET;
+        if (ALLOW_HEAD)
+            if (allow==null) allow=METHOD_HEAD;
+            else allow += ", " + METHOD_HEAD;
+        if (ALLOW_POST)
+            if (allow==null) allow=METHOD_POST;
+            else allow += ", " + METHOD_POST;
+        if (ALLOW_PUT)
+            if (allow==null) allow=METHOD_PUT;
+            else allow += ", " + METHOD_PUT;
+        if (ALLOW_DELETE)
+            if (allow==null) allow=METHOD_DELETE;
+            else allow += ", " + METHOD_DELETE;
+        if (ALLOW_TRACE)
+            if (allow==null) allow=METHOD_TRACE;
+            else allow += ", " + METHOD_TRACE;
+        if (ALLOW_OPTIONS)
+            if (allow==null) allow=METHOD_OPTIONS;
+            else allow += ", " + METHOD_OPTIONS;
+        
+        resp.setHeader("Allow", allow);
+    }
+    
+    
+    /**
+     * Called by the server (via the <code>service</code> method)
+     * to allow a servlet to handle a TRACE request.
+     *
+     * A TRACE returns the headers sent with the TRACE
+     * request to the client, so that they can be used in
+     * debugging. There's no need to override this method. 
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client                                
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              TRACE request
+     *
+     * @exception ServletException  if the request for the
+     *                                  TRACE cannot be handled
+     */
+    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) 
+        throws ServletException, IOException
+    {
+        
+        int responseLength;
+        
+        String CRLF = "\r\n";
+        StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
+            .append(" ").append(req.getProtocol());
+        
+        Enumeration<String> reqHeaderEnum = req.getHeaderNames();
+        
+        while( reqHeaderEnum.hasMoreElements() ) {
+            String headerName = reqHeaderEnum.nextElement();
+            buffer.append(CRLF).append(headerName).append(": ")
+                .append(req.getHeader(headerName)); 
+        }
+        
+        buffer.append(CRLF);
+        
+        responseLength = buffer.length();
+        
+        resp.setContentType("message/http");
+        resp.setContentLength(responseLength);
+        ServletOutputStream out = resp.getOutputStream();
+        out.print(buffer.toString());        
+        out.close();
+        return;
+    }                
+
+
+    /**
+     * Receives standard HTTP requests from the public
+     * <code>service</code> method and dispatches
+     * them to the <code>do</code><i>XXX</i> methods defined in 
+     * this class. This method is an HTTP-specific version of the 
+     * {@link javax.servlet.Servlet#service} method. There's no
+     * need to override this method.
+     *
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param resp  the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client                                
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              HTTP request
+     *
+     * @exception ServletException  if the HTTP request
+     *                                  cannot be handled
+     * 
+     * @see javax.servlet.Servlet#service
+     */
+    protected void service(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        String method = req.getMethod();
+
+        if (method.equals(METHOD_GET)) {
+            long lastModified = getLastModified(req);
+            if (lastModified == -1) {
+                // servlet doesn't support if-modified-since, no reason
+                // to go through further expensive logic
+                doGet(req, resp);
+            } else {
+                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
+                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
+                    // If the servlet mod time is later, call doGet()
+                    // Round down to the nearest second for a proper compare
+                    // A ifModifiedSince of -1 will always be less
+                    maybeSetLastModified(resp, lastModified);
+                    doGet(req, resp);
+                } else {
+                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                }
+            }
+
+        } else if (method.equals(METHOD_HEAD)) {
+            long lastModified = getLastModified(req);
+            maybeSetLastModified(resp, lastModified);
+            doHead(req, resp);
+
+        } else if (method.equals(METHOD_POST)) {
+            doPost(req, resp);
+            
+        } else if (method.equals(METHOD_PUT)) {
+            doPut(req, resp);        
+            
+        } else if (method.equals(METHOD_DELETE)) {
+            doDelete(req, resp);
+            
+        } else if (method.equals(METHOD_OPTIONS)) {
+            doOptions(req,resp);
+            
+        } else if (method.equals(METHOD_TRACE)) {
+            doTrace(req,resp);
+            
+        } else {
+            //
+            // Note that this means NO servlet supports whatever
+            // method was requested, anywhere on this server.
+            //
+
+            String errMsg = lStrings.getString("http.method_not_implemented");
+            Object[] errArgs = new Object[1];
+            errArgs[0] = method;
+            errMsg = MessageFormat.format(errMsg, errArgs);
+            
+            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
+        }
+    }
+
+
+    /*
+     * Sets the Last-Modified entity header field, if it has not
+     * already been set and if the value is meaningful.  Called before
+     * doGet, to ensure that headers are set before response data is
+     * written.  A subclass might have set this header already, so we
+     * check.
+     */
+    private void maybeSetLastModified(HttpServletResponse resp,
+                                      long lastModified) {
+        if (resp.containsHeader(HEADER_LASTMOD))
+            return;
+        if (lastModified >= 0)
+            resp.setDateHeader(HEADER_LASTMOD, lastModified);
+    }
+   
+    
+    /**
+     * Dispatches client requests to the protected
+     * <code>service</code> method. There's no need to
+     * override this method.
+     * 
+     * @param req   the {@link HttpServletRequest} object that
+     *                  contains the request the client made of
+     *                  the servlet
+     *
+     * @param res   the {@link HttpServletResponse} object that
+     *                  contains the response the servlet returns
+     *                  to the client                                
+     *
+     * @exception IOException   if an input or output error occurs
+     *                              while the servlet is handling the
+     *                              HTTP request
+     *
+     * @exception ServletException  if the HTTP request cannot
+     *                                  be handled
+     * 
+     * @see javax.servlet.Servlet#service
+     */
+    @Override
+    public void service(ServletRequest req, ServletResponse res)
+        throws ServletException, IOException {
+
+        HttpServletRequest  request;
+        HttpServletResponse response;
+        
+        try {
+            request = (HttpServletRequest) req;
+            response = (HttpServletResponse) res;
+        } catch (ClassCastException e) {
+            throw new ServletException("non-HTTP request or response");
+        }
+        service(request, response);
+    }
+}
+
+
+/*
+ * A response wrapper for use in (dumb) "HEAD" support.
+ * This just swallows that body, counting the bytes in order to set
+ * the content length appropriately.  All other methods delegate to the
+ * wrapped HTTP Servlet Response object.
+ */
+// file private
+class NoBodyResponse extends HttpServletResponseWrapper {
+    private NoBodyOutputStream                noBody;
+    private PrintWriter                        writer;
+    private boolean                        didSetContentLength;
+
+    // file private
+    NoBodyResponse(HttpServletResponse r) {
+        super(r);
+        noBody = new NoBodyOutputStream();
+    }
+
+    // file private
+    void setContentLength() {
+        if (!didSetContentLength)
+          super.setContentLength(noBody.getContentLength());
+    }
+
+
+    // SERVLET RESPONSE interface methods
+
+    @Override
+    public void setContentLength(int len) {
+        super.setContentLength(len);
+        didSetContentLength = true;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return noBody;
+    }
+
+    @Override
+    public PrintWriter getWriter() throws UnsupportedEncodingException {
+
+        if (writer == null) {
+            OutputStreamWriter w;
+
+            w = new OutputStreamWriter(noBody, getCharacterEncoding());
+            writer = new PrintWriter(w);
+        }
+        return writer;
+    }
+}
+
+
+/*
+ * Servlet output stream that gobbles up all its data.
+ */
+ 
+// file private
+class NoBodyOutputStream extends ServletOutputStream {
+
+    private static final String LSTRING_FILE =
+        "javax.servlet.http.LocalStrings";
+    private static ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+
+    private int                contentLength = 0;
+
+    // file private
+    NoBodyOutputStream() {
+        // NOOP
+    }
+
+    // file private
+    int getContentLength() {
+        return contentLength;
+    }
+
+    @Override
+    public void write(int b) {
+        contentLength++;
+    }
+
+    @Override
+    public void write(byte buf[], int offset, int len)
+        throws IOException
+    {
+        if (len >= 0) {
+            contentLength += len;
+        } else {
+            // XXX
+            // isn't this really an IllegalArgumentException?
+            
+            String msg = lStrings.getString("err.io.negativelength");
+            throw new IOException(msg);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletRequest.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletRequest.java
new file mode 100644
index 0000000..b1b0a72
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletRequest.java
@@ -0,0 +1,486 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+
+/**
+ * Extends the {@link javax.servlet.ServletRequest} interface to provide request
+ * information for HTTP servlets.
+ * <p>
+ * The servlet container creates an <code>HttpServletRequest</code> object and
+ * passes it as an argument to the servlet's service methods (<code>doGet</code>, <code>doPost</code>, etc).
+ * 
+ * @author Various
+ * @version $Version$
+ */
+public interface HttpServletRequest extends ServletRequest {
+
+    /**
+     * String identifier for Basic authentication. Value "BASIC"
+     */
+    public static final String BASIC_AUTH = "BASIC";
+    /**
+     * String identifier for Form authentication. Value "FORM"
+     */
+    public static final String FORM_AUTH = "FORM";
+    /**
+     * String identifier for Client Certificate authentication. Value
+     * "CLIENT_CERT"
+     */
+    public static final String CLIENT_CERT_AUTH = "CLIENT_CERT";
+    /**
+     * String identifier for Digest authentication. Value "DIGEST"
+     */
+    public static final String DIGEST_AUTH = "DIGEST";
+
+    /**
+     * Returns the name of the authentication scheme used to protect the
+     * servlet. All servlet containers support basic, form and client
+     * certificate authentication, and may additionally support digest
+     * authentication. If the servlet is not authenticated <code>null</code> is
+     * returned.
+     * <p>
+     * Same as the value of the CGI variable AUTH_TYPE.
+     * 
+     * @return one of the static members BASIC_AUTH, FORM_AUTH, CLIENT_CERT_AUTH,
+     *         DIGEST_AUTH (suitable for == comparison) or the
+     *         container-specific string indicating the authentication scheme,
+     *         or <code>null</code> if the request was not authenticated.
+     */
+    public String getAuthType();
+
+    /**
+     * Returns an array containing all of the <code>Cookie</code> objects the
+     * client sent with this request. This method returns <code>null</code> if
+     * no cookies were sent.
+     * 
+     * @return an array of all the <code>Cookies</code> included with this
+     *         request, or <code>null</code> if the request has no cookies
+     */
+    public Cookie[] getCookies();
+
+    /**
+     * Returns the value of the specified request header as a <code>long</code>
+     * value that represents a <code>Date</code> object. Use this method with
+     * headers that contain dates, such as <code>If-Modified-Since</code>.
+     * <p>
+     * The date is returned as the number of milliseconds since January 1, 1970
+     * GMT. The header name is case insensitive.
+     * <p>
+     * If the request did not have a header of the specified name, this method
+     * returns -1. If the header can't be converted to a date, the method throws
+     * an <code>IllegalArgumentException</code>.
+     * 
+     * @param name
+     *            a <code>String</code> specifying the name of the header
+     * @return a <code>long</code> value representing the date specified in the
+     *         header expressed as the number of milliseconds since January 1,
+     *         1970 GMT, or -1 if the named header was not included with the
+     *         request
+     * @exception IllegalArgumentException
+     *                If the header value can't be converted to a date
+     */
+    public long getDateHeader(String name);
+
+    /**
+     * Returns the value of the specified request header as a
+     * <code>String</code>. If the request did not include a header of the
+     * specified name, this method returns <code>null</code>. If there are
+     * multiple headers with the same name, this method returns the first head
+     * in the request. The header name is case insensitive. You can use this
+     * method with any request header.
+     * 
+     * @param name
+     *            a <code>String</code> specifying the header name
+     * @return a <code>String</code> containing the value of the requested
+     *         header, or <code>null</code> if the request does not have a
+     *         header of that name
+     */
+    public String getHeader(String name);
+
+    /**
+     * Returns all the values of the specified request header as an
+     * <code>Enumeration</code> of <code>String</code> objects.
+     * <p>
+     * Some headers, such as <code>Accept-Language</code> can be sent by clients
+     * as several headers each with a different value rather than sending the
+     * header as a comma separated list.
+     * <p>
+     * If the request did not include any headers of the specified name, this
+     * method returns an empty <code>Enumeration</code>. The header name is case
+     * insensitive. You can use this method with any request header.
+     * 
+     * @param name
+     *            a <code>String</code> specifying the header name
+     * @return an <code>Enumeration</code> containing the values of the requested
+     *         header. If the request does not have any headers of that name
+     *         return an empty enumeration. If the container does not allow
+     *         access to header information, return null
+     */
+    public Enumeration<String> getHeaders(String name);
+
+    /**
+     * Returns an enumeration of all the header names this request contains. If
+     * the request has no headers, this method returns an empty enumeration.
+     * <p>
+     * Some servlet containers do not allow servlets to access headers using
+     * this method, in which case this method returns <code>null</code>
+     * 
+     * @return an enumeration of all the header names sent with this request; if
+     *         the request has no headers, an empty enumeration; if the servlet
+     *         container does not allow servlets to use this method,
+     *         <code>null</code>
+     */
+    public Enumeration<String> getHeaderNames();
+
+    /**
+     * Returns the value of the specified request header as an <code>int</code>.
+     * If the request does not have a header of the specified name, this method
+     * returns -1. If the header cannot be converted to an integer, this method
+     * throws a <code>NumberFormatException</code>.
+     * <p>
+     * The header name is case insensitive.
+     * 
+     * @param name
+     *            a <code>String</code> specifying the name of a request header
+     * @return an integer expressing the value of the request header or -1 if the
+     *         request doesn't have a header of this name
+     * @exception NumberFormatException
+     *                If the header value can't be converted to an
+     *                <code>int</code>
+     */
+    public int getIntHeader(String name);
+
+    /**
+     * Returns the name of the HTTP method with which this request was made, for
+     * example, GET, POST, or PUT. Same as the value of the CGI variable
+     * REQUEST_METHOD.
+     * 
+     * @return a <code>String</code> specifying the name of the method with
+     *         which this request was made
+     */
+    public String getMethod();
+
+    /**
+     * Returns any extra path information associated with the URL the client
+     * sent when it made this request. The extra path information follows the
+     * servlet path but precedes the query string and will start with a "/"
+     * character.
+     * <p>
+     * This method returns <code>null</code> if there was no extra path
+     * information.
+     * <p>
+     * Same as the value of the CGI variable PATH_INFO.
+     * 
+     * @return a <code>String</code>, decoded by the web container, specifying
+     *         extra path information that comes after the servlet path but
+     *         before the query string in the request URL; or <code>null</code>
+     *         if the URL does not have any extra path information
+     */
+    public String getPathInfo();
+
+    /**
+     * Returns any extra path information after the servlet name but before the
+     * query string, and translates it to a real path. Same as the value of the
+     * CGI variable PATH_TRANSLATED.
+     * <p>
+     * If the URL does not have any extra path information, this method returns
+     * <code>null</code> or the servlet container cannot translate the virtual
+     * path to a real path for any reason (such as when the web application is
+     * executed from an archive). The web container does not decode this string.
+     * 
+     * @return a <code>String</code> specifying the real path, or
+     *         <code>null</code> if the URL does not have any extra path
+     *         information
+     */
+    public String getPathTranslated();
+
+    /**
+     * Returns the portion of the request URI that indicates the context of the
+     * request. The context path always comes first in a request URI. The path
+     * starts with a "/" character but does not end with a "/" character. For
+     * servlets in the default (root) context, this method returns "". The
+     * container does not decode this string.
+     * 
+     * @return a <code>String</code> specifying the portion of the request URI
+     *         that indicates the context of the request
+     */
+    public String getContextPath();
+
+    /**
+     * Returns the query string that is contained in the request URL after the
+     * path. This method returns <code>null</code> if the URL does not have a
+     * query string. Same as the value of the CGI variable QUERY_STRING.
+     * 
+     * @return a <code>String</code> containing the query string or
+     *         <code>null</code> if the URL contains no query string. The value
+     *         is not decoded by the container.
+     */
+    public String getQueryString();
+
+    /**
+     * Returns the login of the user making this request, if the user has been
+     * authenticated, or <code>null</code> if the user has not been
+     * authenticated. Whether the user name is sent with each subsequent request
+     * depends on the browser and type of authentication. Same as the value of
+     * the CGI variable REMOTE_USER.
+     * 
+     * @return a <code>String</code> specifying the login of the user making
+     *         this request, or <code>null</code> if the user login is not known
+     */
+    public String getRemoteUser();
+
+    /**
+     * Returns a boolean indicating whether the authenticated user is included
+     * in the specified logical "role". Roles and role membership can be defined
+     * using deployment descriptors. If the user has not been authenticated, the
+     * method returns <code>false</code>.
+     * 
+     * @param role
+     *            a <code>String</code> specifying the name of the role
+     * @return a <code>boolean</code> indicating whether the user making this
+     *         request belongs to a given role; <code>false</code> if the user
+     *         has not been authenticated
+     */
+    public boolean isUserInRole(String role);
+
+    /**
+     * Returns a <code>java.security.Principal</code> object containing the name
+     * of the current authenticated user. If the user has not been
+     * authenticated, the method returns <code>null</code>.
+     * 
+     * @return a <code>java.security.Principal</code> containing the name of the
+     *         user making this request; <code>null</code> if the user has not
+     *         been authenticated
+     */
+    public java.security.Principal getUserPrincipal();
+
+    /**
+     * Returns the session ID specified by the client. This may not be the same
+     * as the ID of the current valid session for this request. If the client
+     * did not specify a session ID, this method returns <code>null</code>.
+     * 
+     * @return a <code>String</code> specifying the session ID, or
+     *         <code>null</code> if the request did not specify a session ID
+     * @see #isRequestedSessionIdValid
+     */
+    public String getRequestedSessionId();
+
+    /**
+     * Returns the part of this request's URL from the protocol name up to the
+     * query string in the first line of the HTTP request. The web container
+     * does not decode this String. For example:
+     * <table summary="Examples of Returned Values">
+     * <tr align=left>
+     * <th>First line of HTTP request</th>
+     * <th>Returned Value</th>
+     * <tr>
+     * <td>POST /some/path.html HTTP/1.1
+     * <td>
+     * <td>/some/path.html
+     * <tr>
+     * <td>GET http://foo.bar/a.html HTTP/1.0
+     * <td>
+     * <td>/a.html
+     * <tr>
+     * <td>HEAD /xyz?a=b HTTP/1.1
+     * <td>
+     * <td>/xyz
+     * </table>
+     * <p>
+     * To reconstruct an URL with a scheme and host, use
+     * {@link HttpUtils#getRequestURL}.
+     * 
+     * @return a <code>String</code> containing the part of the URL from the
+     *         protocol name up to the query string
+     * @see HttpUtils#getRequestURL
+     */
+    public String getRequestURI();
+
+    /**
+     * Reconstructs the URL the client used to make the request. The returned
+     * URL contains a protocol, server name, port number, and server path, but
+     * it does not include query string parameters.
+     * <p>
+     * Because this method returns a <code>StringBuffer</code>, not a string,
+     * you can modify the URL easily, for example, to append query parameters.
+     * <p>
+     * This method is useful for creating redirect messages and for reporting
+     * errors.
+     * 
+     * @return a <code>StringBuffer</code> object containing the reconstructed
+     *         URL
+     */
+    public StringBuffer getRequestURL();
+
+    /**
+     * Returns the part of this request's URL that calls the servlet. This path
+     * starts with a "/" character and includes either the servlet name or a
+     * path to the servlet, but does not include any extra path information or a
+     * query string. Same as the value of the CGI variable SCRIPT_NAME.
+     * <p>
+     * This method will return an empty string ("") if the servlet used to
+     * process this request was matched using the "/*" pattern.
+     * 
+     * @return a <code>String</code> containing the name or path of the servlet
+     *         being called, as specified in the request URL, decoded, or an
+     *         empty string if the servlet used to process the request is
+     *         matched using the "/*" pattern.
+     */
+    public String getServletPath();
+
+    /**
+     * Returns the current <code>HttpSession</code> associated with this request
+     * or, if there is no current session and <code>create</code> is true,
+     * returns a new session.
+     * <p>
+     * If <code>create</code> is <code>false</code> and the request has no valid
+     * <code>HttpSession</code>, this method returns <code>null</code>.
+     * <p>
+     * To make sure the session is properly maintained, you must call this
+     * method before the response is committed. If the container is using
+     * cookies to maintain session integrity and is asked to create a new
+     * session when the response is committed, an IllegalStateException is
+     * thrown.
+     * 
+     * @param create
+     *            <code>true</code> to create a new session for this request if
+     *            necessary; <code>false</code> to return <code>null</code> if
+     *            there's no current session
+     * @return the <code>HttpSession</code> associated with this request or
+     *         <code>null</code> if <code>create</code> is <code>false</code>
+     *         and the request has no valid session
+     * @see #getSession()
+     */
+    public HttpSession getSession(boolean create);
+
+    /**
+     * Returns the current session associated with this request, or if the
+     * request does not have a session, creates one.
+     * 
+     * @return the <code>HttpSession</code> associated with this request
+     * @see #getSession(boolean)
+     */
+    public HttpSession getSession();
+
+    /**
+     * Checks whether the requested session ID is still valid.
+     * 
+     * @return <code>true</code> if this request has an id for a valid session
+     *         in the current session context; <code>false</code> otherwise
+     * @see #getRequestedSessionId
+     * @see #getSession
+     * @see HttpSessionContext
+     */
+    public boolean isRequestedSessionIdValid();
+
+    /**
+     * Checks whether the requested session ID came in as a cookie.
+     * 
+     * @return <code>true</code> if the session ID came in as a cookie;
+     *         otherwise, <code>false</code>
+     * @see #getSession
+     */
+    public boolean isRequestedSessionIdFromCookie();
+
+    /**
+     * Checks whether the requested session ID came in as part of the request
+     * URL.
+     * 
+     * @return <code>true</code> if the session ID came in as part of a URL;
+     *         otherwise, <code>false</code>
+     * @see #getSession
+     */
+    public boolean isRequestedSessionIdFromURL();
+
+    /**
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *             {@link #isRequestedSessionIdFromURL} instead.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public boolean isRequestedSessionIdFromUrl();
+
+    /**
+     * @param response
+     * @return TODO
+     * @throws IOException
+     * @throws ServletException
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public boolean authenticate(HttpServletResponse response)
+            throws IOException, ServletException;
+
+    /**
+     * @param username
+     * @param password
+     * @throws ServletException
+     *             If any of {@link #getRemoteUser()},
+     *             {@link #getUserPrincipal()} or {@link #getAuthType()} are
+     *             non-null, if the configured authenticator does not support
+     *             user name and password authentication or if the
+     *             authentication fails
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public void login(String username, String password) throws ServletException;
+
+    /**
+     * @throws ServletException
+     *             If the logout fails
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public void logout() throws ServletException;
+
+    /**
+     * Return a collection of all uploaded Parts.
+     * 
+     * @return A collection of all uploaded Parts.
+     * @throws IOException
+     *             if an I/O error occurs
+     * @throws IllegalStateException
+     *             if size limits are exceeded
+     * @throws ServletException
+     *             if the request is not multipart/form-data
+     * @since Servlet 3.0
+     */
+    public Collection<Part> getParts() throws IOException,
+            IllegalStateException, ServletException;
+
+    /**
+     * Gets the named Part or null if the Part does not exist. Triggers upload
+     * of all Parts.
+     * 
+     * @param name
+     * @return The named Part or null if the Part does not exist
+     * @throws IOException
+     *             if an I/O error occurs
+     * @throws IllegalStateException
+     *             if size limits are exceeded
+     * @throws ServletException
+     *             if the request is not multipart/form-data
+     * @since Servlet 3.0
+     */
+    public Part getPart(String name) throws IOException, IllegalStateException,
+            ServletException;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletRequestWrapper.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletRequestWrapper.java
new file mode 100644
index 0000000..1d3b329
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletRequestWrapper.java
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequestWrapper;
+
+/**
+ * Provides a convenient implementation of the HttpServletRequest interface that
+ * can be subclassed by developers wishing to adapt the request to a Servlet.
+ * This class implements the Wrapper or Decorator pattern. Methods default to
+ * calling through to the wrapped request object.
+ * 
+ * @see javax.servlet.http.HttpServletRequest
+ * @since v 2.3
+ */
+public class HttpServletRequestWrapper extends ServletRequestWrapper implements
+        HttpServletRequest {
+
+    /**
+     * Constructs a request object wrapping the given request.
+     * 
+     * @throws java.lang.IllegalArgumentException
+     *             if the request is null
+     */
+    public HttpServletRequestWrapper(HttpServletRequest request) {
+        super(request);
+    }
+
+    private HttpServletRequest _getHttpServletRequest() {
+        return (HttpServletRequest) super.getRequest();
+    }
+
+    /**
+     * The default behavior of this method is to return getAuthType() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getAuthType() {
+        return this._getHttpServletRequest().getAuthType();
+    }
+
+    /**
+     * The default behavior of this method is to return getCookies() on the
+     * wrapped request object.
+     */
+    @Override
+    public Cookie[] getCookies() {
+        return this._getHttpServletRequest().getCookies();
+    }
+
+    /**
+     * The default behavior of this method is to return getDateHeader(String
+     * name) on the wrapped request object.
+     */
+    @Override
+    public long getDateHeader(String name) {
+        return this._getHttpServletRequest().getDateHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getHeader(String name)
+     * on the wrapped request object.
+     */
+    @Override
+    public String getHeader(String name) {
+        return this._getHttpServletRequest().getHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getHeaders(String name)
+     * on the wrapped request object.
+     */
+    @Override
+    public Enumeration<String> getHeaders(String name) {
+        return this._getHttpServletRequest().getHeaders(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getHeaderNames() on the
+     * wrapped request object.
+     */
+    @Override
+    public Enumeration<String> getHeaderNames() {
+        return this._getHttpServletRequest().getHeaderNames();
+    }
+
+    /**
+     * The default behavior of this method is to return getIntHeader(String
+     * name) on the wrapped request object.
+     */
+    @Override
+    public int getIntHeader(String name) {
+        return this._getHttpServletRequest().getIntHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to return getMethod() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getMethod() {
+        return this._getHttpServletRequest().getMethod();
+    }
+
+    /**
+     * The default behavior of this method is to return getPathInfo() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getPathInfo() {
+        return this._getHttpServletRequest().getPathInfo();
+    }
+
+    /**
+     * The default behavior of this method is to return getPathTranslated() on
+     * the wrapped request object.
+     */
+    @Override
+    public String getPathTranslated() {
+        return this._getHttpServletRequest().getPathTranslated();
+    }
+
+    /**
+     * The default behavior of this method is to return getContextPath() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getContextPath() {
+        return this._getHttpServletRequest().getContextPath();
+    }
+
+    /**
+     * The default behavior of this method is to return getQueryString() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getQueryString() {
+        return this._getHttpServletRequest().getQueryString();
+    }
+
+    /**
+     * The default behavior of this method is to return getRemoteUser() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getRemoteUser() {
+        return this._getHttpServletRequest().getRemoteUser();
+    }
+
+    /**
+     * The default behavior of this method is to return isUserInRole(String
+     * role) on the wrapped request object.
+     */
+    @Override
+    public boolean isUserInRole(String role) {
+        return this._getHttpServletRequest().isUserInRole(role);
+    }
+
+    /**
+     * The default behavior of this method is to return getUserPrincipal() on
+     * the wrapped request object.
+     */
+    @Override
+    public java.security.Principal getUserPrincipal() {
+        return this._getHttpServletRequest().getUserPrincipal();
+    }
+
+    /**
+     * The default behavior of this method is to return getRequestedSessionId()
+     * on the wrapped request object.
+     */
+    @Override
+    public String getRequestedSessionId() {
+        return this._getHttpServletRequest().getRequestedSessionId();
+    }
+
+    /**
+     * The default behavior of this method is to return getRequestURI() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getRequestURI() {
+        return this._getHttpServletRequest().getRequestURI();
+    }
+
+    /**
+     * The default behavior of this method is to return getRequestURL() on the
+     * wrapped request object.
+     */
+    @Override
+    public StringBuffer getRequestURL() {
+        return this._getHttpServletRequest().getRequestURL();
+    }
+
+    /**
+     * The default behavior of this method is to return getServletPath() on the
+     * wrapped request object.
+     */
+    @Override
+    public String getServletPath() {
+        return this._getHttpServletRequest().getServletPath();
+    }
+
+    /**
+     * The default behavior of this method is to return getSession(boolean
+     * create) on the wrapped request object.
+     */
+    @Override
+    public HttpSession getSession(boolean create) {
+        return this._getHttpServletRequest().getSession(create);
+    }
+
+    /**
+     * The default behavior of this method is to return getSession() on the
+     * wrapped request object.
+     */
+    @Override
+    public HttpSession getSession() {
+        return this._getHttpServletRequest().getSession();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdValid() on the wrapped request object.
+     */
+    @Override
+    public boolean isRequestedSessionIdValid() {
+        return this._getHttpServletRequest().isRequestedSessionIdValid();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdFromCookie() on the wrapped request object.
+     */
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+        return this._getHttpServletRequest().isRequestedSessionIdFromCookie();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdFromURL() on the wrapped request object.
+     */
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+        return this._getHttpServletRequest().isRequestedSessionIdFromURL();
+    }
+
+    /**
+     * The default behavior of this method is to return
+     * isRequestedSessionIdFromUrl() on the wrapped request object.
+     * 
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public boolean isRequestedSessionIdFromUrl() {
+        return this._getHttpServletRequest().isRequestedSessionIdFromUrl();
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public boolean authenticate(HttpServletResponse response)
+            throws IOException, ServletException {
+        return this._getHttpServletRequest().authenticate(response);
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public void login(String username, String password) throws ServletException {
+        this._getHttpServletRequest().login(username, password);
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public void logout() throws ServletException {
+        this._getHttpServletRequest().logout();
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public Collection<Part> getParts() throws IllegalStateException,
+            IOException, ServletException {
+        return this._getHttpServletRequest().getParts();
+    }
+
+    /**
+     * @throws ServletException
+     * @throws IOException
+     * @throws IllegalStateException
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public Part getPart(String name) throws IllegalStateException, IOException,
+            ServletException {
+        return this._getHttpServletRequest().getPart(name);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletResponse.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletResponse.java
new file mode 100644
index 0000000..c111a4d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletResponse.java
@@ -0,0 +1,592 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponse;
+
+/**
+ * Extends the {@link ServletResponse} interface to provide HTTP-specific
+ * functionality in sending a response. For example, it has methods to access
+ * HTTP headers and cookies.
+ * <p>
+ * The servlet container creates an <code>HttpServletResponse</code> object and
+ * passes it as an argument to the servlet's service methods (<code>doGet</code>, <code>doPost</code>, etc).
+ * 
+ * @author Various
+ * @version $Version$
+ * @see javax.servlet.ServletResponse
+ */
+public interface HttpServletResponse extends ServletResponse {
+
+    /**
+     * Adds the specified cookie to the response. This method can be called
+     * multiple times to set more than one cookie.
+     * 
+     * @param cookie
+     *            the Cookie to return to the client
+     */
+    public void addCookie(Cookie cookie);
+
+    /**
+     * Returns a boolean indicating whether the named response header has
+     * already been set.
+     * 
+     * @param name
+     *            the header name
+     * @return <code>true</code> if the named response header has already been
+     *         set; <code>false</code> otherwise
+     */
+    public boolean containsHeader(String name);
+
+    /**
+     * Encodes the specified URL by including the session ID in it, or, if
+     * encoding is not needed, returns the URL unchanged. The implementation of
+     * this method includes the logic to determine whether the session ID needs
+     * to be encoded in the URL. For example, if the browser supports cookies,
+     * or session tracking is turned off, URL encoding is unnecessary.
+     * <p>
+     * For robust session tracking, all URLs emitted by a servlet should be run
+     * through this method. Otherwise, URL rewriting cannot be used with
+     * browsers which do not support cookies.
+     * 
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     */
+    public String encodeURL(String url);
+
+    /**
+     * Encodes the specified URL for use in the <code>sendRedirect</code> method
+     * or, if encoding is not needed, returns the URL unchanged. The
+     * implementation of this method includes the logic to determine whether the
+     * session ID needs to be encoded in the URL. Because the rules for making
+     * this determination can differ from those used to decide whether to encode
+     * a normal link, this method is separated from the <code>encodeURL</code>
+     * method.
+     * <p>
+     * All URLs sent to the <code>HttpServletResponse.sendRedirect</code> method
+     * should be run through this method. Otherwise, URL rewriting cannot be
+     * used with browsers which do not support cookies.
+     * 
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     * @see #sendRedirect
+     * @see #encodeUrl
+     */
+    public String encodeRedirectURL(String url);
+
+    /**
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     * @deprecated As of version 2.1, use encodeURL(String url) instead
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeUrl(String url);
+
+    /**
+     * @param url
+     *            the url to be encoded.
+     * @return the encoded URL if encoding is needed; the unchanged URL
+     *         otherwise.
+     * @deprecated As of version 2.1, use encodeRedirectURL(String url) instead
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeRedirectUrl(String url);
+
+    /**
+     * Sends an error response to the client using the specified status. The
+     * server defaults to creating the response to look like an HTML-formatted
+     * server error page containing the specified message, setting the content
+     * type to "text/html", leaving cookies and other headers unmodified. If an
+     * error-page declaration has been made for the web application
+     * corresponding to the status code passed in, it will be served back in
+     * preference to the suggested msg parameter.
+     * <p>
+     * If the response has already been committed, this method throws an
+     * IllegalStateException. After using this method, the response should be
+     * considered to be committed and should not be written to.
+     * 
+     * @param sc
+     *            the error status code
+     * @param msg
+     *            the descriptive message
+     * @exception IOException
+     *                If an input or output exception occurs
+     * @exception IllegalStateException
+     *                If the response was committed
+     */
+    public void sendError(int sc, String msg) throws IOException;
+
+    /**
+     * Sends an error response to the client using the specified status code and
+     * clearing the buffer.
+     * <p>
+     * If the response has already been committed, this method throws an
+     * IllegalStateException. After using this method, the response should be
+     * considered to be committed and should not be written to.
+     * 
+     * @param sc
+     *            the error status code
+     * @exception IOException
+     *                If an input or output exception occurs
+     * @exception IllegalStateException
+     *                If the response was committed before this method call
+     */
+    public void sendError(int sc) throws IOException;
+
+    /**
+     * Sends a temporary redirect response to the client using the specified
+     * redirect location URL. This method can accept relative URLs; the servlet
+     * container must convert the relative URL to an absolute URL before sending
+     * the response to the client. If the location is relative without a leading
+     * '/' the container interprets it as relative to the current request URI.
+     * If the location is relative with a leading '/' the container interprets
+     * it as relative to the servlet container root.
+     * <p>
+     * If the response has already been committed, this method throws an
+     * IllegalStateException. After using this method, the response should be
+     * considered to be committed and should not be written to.
+     * 
+     * @param location
+     *            the redirect location URL
+     * @exception IOException
+     *                If an input or output exception occurs
+     * @exception IllegalStateException
+     *                If the response was committed or if a partial URL is given
+     *                and cannot be converted into a valid URL
+     */
+    public void sendRedirect(String location) throws IOException;
+
+    /**
+     * Sets a response header with the given name and date-value. The date is
+     * specified in terms of milliseconds since the epoch. If the header had
+     * already been set, the new value overwrites the previous one. The
+     * <code>containsHeader</code> method can be used to test for the presence
+     * of a header before setting its value.
+     * 
+     * @param name
+     *            the name of the header to set
+     * @param date
+     *            the assigned date value
+     * @see #containsHeader
+     * @see #addDateHeader
+     */
+    public void setDateHeader(String name, long date);
+
+    /**
+     * Adds a response header with the given name and date-value. The date is
+     * specified in terms of milliseconds since the epoch. This method allows
+     * response headers to have multiple values.
+     * 
+     * @param name
+     *            the name of the header to set
+     * @param date
+     *            the additional date value
+     * @see #setDateHeader
+     */
+    public void addDateHeader(String name, long date);
+
+    /**
+     * Sets a response header with the given name and value. If the header had
+     * already been set, the new value overwrites the previous one. The
+     * <code>containsHeader</code> method can be used to test for the presence
+     * of a header before setting its value.
+     * 
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the header value If it contains octet string, it should be
+     *            encoded according to RFC 2047
+     *            (http://www.ietf.org/rfc/rfc2047.txt)
+     * @see #containsHeader
+     * @see #addHeader
+     */
+    public void setHeader(String name, String value);
+
+    /**
+     * Adds a response header with the given name and value. This method allows
+     * response headers to have multiple values.
+     * 
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the additional header value If it contains octet string, it
+     *            should be encoded according to RFC 2047
+     *            (http://www.ietf.org/rfc/rfc2047.txt)
+     * @see #setHeader
+     */
+    public void addHeader(String name, String value);
+
+    /**
+     * Sets a response header with the given name and integer value. If the
+     * header had already been set, the new value overwrites the previous one.
+     * The <code>containsHeader</code> method can be used to test for the
+     * presence of a header before setting its value.
+     * 
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the assigned integer value
+     * @see #containsHeader
+     * @see #addIntHeader
+     */
+    public void setIntHeader(String name, int value);
+
+    /**
+     * Adds a response header with the given name and integer value. This method
+     * allows response headers to have multiple values.
+     * 
+     * @param name
+     *            the name of the header
+     * @param value
+     *            the assigned integer value
+     * @see #setIntHeader
+     */
+    public void addIntHeader(String name, int value);
+
+    /**
+     * Sets the status code for this response. This method is used to set the
+     * return status code when there is no error (for example, for the status
+     * codes SC_OK or SC_MOVED_TEMPORARILY). If there is an error, and the
+     * caller wishes to invoke an error-page defined in the web application, the
+     * <code>sendError</code> method should be used instead.
+     * <p>
+     * The container clears the buffer and sets the Location header, preserving
+     * cookies and other headers.
+     * 
+     * @param sc
+     *            the status code
+     * @see #sendError
+     */
+    public void setStatus(int sc);
+
+    /**
+     * Sets the status code and message for this response.
+     * 
+     * @param sc
+     *            the status code
+     * @param sm
+     *            the status message
+     * @deprecated As of version 2.1, due to ambiguous meaning of the message
+     *             parameter. To set a status code use
+     *             <code>setStatus(int)</code>, to send an error with a
+     *             description use <code>sendError(int, String)</code>.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void setStatus(int sc, String sm);
+
+    /**
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public int getStatus();
+
+    /**
+     * @param name
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public String getHeader(String name);
+
+    /**
+     * @param name
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public Collection<String> getHeaders(String name);
+
+    /**
+     * @return TODO
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    public Collection<String> getHeaderNames();
+
+    /*
+     * Server status codes; see RFC 2068.
+     */
+
+    /**
+     * Status code (100) indicating the client can continue.
+     */
+    public static final int SC_CONTINUE = 100;
+
+    /**
+     * Status code (101) indicating the server is switching protocols according
+     * to Upgrade header.
+     */
+    public static final int SC_SWITCHING_PROTOCOLS = 101;
+
+    /**
+     * Status code (200) indicating the request succeeded normally.
+     */
+    public static final int SC_OK = 200;
+
+    /**
+     * Status code (201) indicating the request succeeded and created a new
+     * resource on the server.
+     */
+    public static final int SC_CREATED = 201;
+
+    /**
+     * Status code (202) indicating that a request was accepted for processing,
+     * but was not completed.
+     */
+    public static final int SC_ACCEPTED = 202;
+
+    /**
+     * Status code (203) indicating that the meta information presented by the
+     * client did not originate from the server.
+     */
+    public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
+
+    /**
+     * Status code (204) indicating that the request succeeded but that there
+     * was no new information to return.
+     */
+    public static final int SC_NO_CONTENT = 204;
+
+    /**
+     * Status code (205) indicating that the agent <em>SHOULD</em> reset the
+     * document view which caused the request to be sent.
+     */
+    public static final int SC_RESET_CONTENT = 205;
+
+    /**
+     * Status code (206) indicating that the server has fulfilled the partial
+     * GET request for the resource.
+     */
+    public static final int SC_PARTIAL_CONTENT = 206;
+
+    /**
+     * Status code (300) indicating that the requested resource corresponds to
+     * any one of a set of representations, each with its own specific location.
+     */
+    public static final int SC_MULTIPLE_CHOICES = 300;
+
+    /**
+     * Status code (301) indicating that the resource has permanently moved to a
+     * new location, and that future references should use a new URI with their
+     * requests.
+     */
+    public static final int SC_MOVED_PERMANENTLY = 301;
+
+    /**
+     * Status code (302) indicating that the resource has temporarily moved to
+     * another location, but that future references should still use the
+     * original URI to access the resource. This definition is being retained
+     * for backwards compatibility. SC_FOUND is now the preferred definition.
+     */
+    public static final int SC_MOVED_TEMPORARILY = 302;
+
+    /**
+     * Status code (302) indicating that the resource reside temporarily under a
+     * different URI. Since the redirection might be altered on occasion, the
+     * client should continue to use the Request-URI for future
+     * requests.(HTTP/1.1) To represent the status code (302), it is recommended
+     * to use this variable.
+     */
+    public static final int SC_FOUND = 302;
+
+    /**
+     * Status code (303) indicating that the response to the request can be
+     * found under a different URI.
+     */
+    public static final int SC_SEE_OTHER = 303;
+
+    /**
+     * Status code (304) indicating that a conditional GET operation found that
+     * the resource was available and not modified.
+     */
+    public static final int SC_NOT_MODIFIED = 304;
+
+    /**
+     * Status code (305) indicating that the requested resource <em>MUST</em> be
+     * accessed through the proxy given by the <code><em>Location</em></code>
+     * field.
+     */
+    public static final int SC_USE_PROXY = 305;
+
+    /**
+     * Status code (307) indicating that the requested resource resides
+     * temporarily under a different URI. The temporary URI <em>SHOULD</em> be
+     * given by the <code><em>Location</em></code> field in the response.
+     */
+    public static final int SC_TEMPORARY_REDIRECT = 307;
+
+    /**
+     * Status code (400) indicating the request sent by the client was
+     * syntactically incorrect.
+     */
+    public static final int SC_BAD_REQUEST = 400;
+
+    /**
+     * Status code (401) indicating that the request requires HTTP
+     * authentication.
+     */
+    public static final int SC_UNAUTHORIZED = 401;
+
+    /**
+     * Status code (402) reserved for future use.
+     */
+    public static final int SC_PAYMENT_REQUIRED = 402;
+
+    /**
+     * Status code (403) indicating the server understood the request but
+     * refused to fulfill it.
+     */
+    public static final int SC_FORBIDDEN = 403;
+
+    /**
+     * Status code (404) indicating that the requested resource is not
+     * available.
+     */
+    public static final int SC_NOT_FOUND = 404;
+
+    /**
+     * Status code (405) indicating that the method specified in the
+     * <code><em>Request-Line</em></code> is not allowed for the resource
+     * identified by the <code><em>Request-URI</em></code>.
+     */
+    public static final int SC_METHOD_NOT_ALLOWED = 405;
+
+    /**
+     * Status code (406) indicating that the resource identified by the request
+     * is only capable of generating response entities which have content
+     * characteristics not acceptable according to the accept headers sent in
+     * the request.
+     */
+    public static final int SC_NOT_ACCEPTABLE = 406;
+
+    /**
+     * Status code (407) indicating that the client <em>MUST</em> first
+     * authenticate itself with the proxy.
+     */
+    public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+
+    /**
+     * Status code (408) indicating that the client did not produce a request
+     * within the time that the server was prepared to wait.
+     */
+    public static final int SC_REQUEST_TIMEOUT = 408;
+
+    /**
+     * Status code (409) indicating that the request could not be completed due
+     * to a conflict with the current state of the resource.
+     */
+    public static final int SC_CONFLICT = 409;
+
+    /**
+     * Status code (410) indicating that the resource is no longer available at
+     * the server and no forwarding address is known. This condition
+     * <em>SHOULD</em> be considered permanent.
+     */
+    public static final int SC_GONE = 410;
+
+    /**
+     * Status code (411) indicating that the request cannot be handled without a
+     * defined <code><em>Content-Length</em></code>.
+     */
+    public static final int SC_LENGTH_REQUIRED = 411;
+
+    /**
+     * Status code (412) indicating that the precondition given in one or more
+     * of the request-header fields evaluated to false when it was tested on the
+     * server.
+     */
+    public static final int SC_PRECONDITION_FAILED = 412;
+
+    /**
+     * Status code (413) indicating that the server is refusing to process the
+     * request because the request entity is larger than the server is willing
+     * or able to process.
+     */
+    public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
+
+    /**
+     * Status code (414) indicating that the server is refusing to service the
+     * request because the <code><em>Request-URI</em></code> is longer than the
+     * server is willing to interpret.
+     */
+    public static final int SC_REQUEST_URI_TOO_LONG = 414;
+
+    /**
+     * Status code (415) indicating that the server is refusing to service the
+     * request because the entity of the request is in a format not supported by
+     * the requested resource for the requested method.
+     */
+    public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+
+    /**
+     * Status code (416) indicating that the server cannot serve the requested
+     * byte range.
+     */
+    public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+
+    /**
+     * Status code (417) indicating that the server could not meet the
+     * expectation given in the Expect request header.
+     */
+    public static final int SC_EXPECTATION_FAILED = 417;
+
+    /**
+     * Status code (500) indicating an error inside the HTTP server which
+     * prevented it from fulfilling the request.
+     */
+    public static final int SC_INTERNAL_SERVER_ERROR = 500;
+
+    /**
+     * Status code (501) indicating the HTTP server does not support the
+     * functionality needed to fulfill the request.
+     */
+    public static final int SC_NOT_IMPLEMENTED = 501;
+
+    /**
+     * Status code (502) indicating that the HTTP server received an invalid
+     * response from a server it consulted when acting as a proxy or gateway.
+     */
+    public static final int SC_BAD_GATEWAY = 502;
+
+    /**
+     * Status code (503) indicating that the HTTP server is temporarily
+     * overloaded, and unable to handle the request.
+     */
+    public static final int SC_SERVICE_UNAVAILABLE = 503;
+
+    /**
+     * Status code (504) indicating that the server did not receive a timely
+     * response from the upstream server while acting as a gateway or proxy.
+     */
+    public static final int SC_GATEWAY_TIMEOUT = 504;
+
+    /**
+     * Status code (505) indicating that the server does not support or refuses
+     * to support the HTTP protocol version that was used in the request
+     * message.
+     */
+    public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletResponseWrapper.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletResponseWrapper.java
new file mode 100644
index 0000000..5162906
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpServletResponseWrapper.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponseWrapper;
+
+/**
+ * Provides a convenient implementation of the HttpServletResponse interface
+ * that can be subclassed by developers wishing to adapt the response from a
+ * Servlet. This class implements the Wrapper or Decorator pattern. Methods
+ * default to calling through to the wrapped response object.
+ * 
+ * @author Various
+ * @version $Version$
+ * @since v 2.3
+ * @see javax.servlet.http.HttpServletResponse
+ */
+public class HttpServletResponseWrapper extends ServletResponseWrapper
+        implements HttpServletResponse {
+
+    /**
+     * Constructs a response adaptor wrapping the given response.
+     * 
+     * @throws java.lang.IllegalArgumentException
+     *             if the response is null
+     */
+    public HttpServletResponseWrapper(HttpServletResponse response) {
+        super(response);
+    }
+
+    private HttpServletResponse _getHttpServletResponse() {
+        return (HttpServletResponse) super.getResponse();
+    }
+
+    /**
+     * The default behavior of this method is to call addCookie(Cookie cookie)
+     * on the wrapped response object.
+     */
+    @Override
+    public void addCookie(Cookie cookie) {
+        this._getHttpServletResponse().addCookie(cookie);
+    }
+
+    /**
+     * The default behavior of this method is to call containsHeader(String
+     * name) on the wrapped response object.
+     */
+    @Override
+    public boolean containsHeader(String name) {
+        return this._getHttpServletResponse().containsHeader(name);
+    }
+
+    /**
+     * The default behavior of this method is to call encodeURL(String url) on
+     * the wrapped response object.
+     */
+    @Override
+    public String encodeURL(String url) {
+        return this._getHttpServletResponse().encodeURL(url);
+    }
+
+    /**
+     * The default behavior of this method is to return encodeRedirectURL(String
+     * url) on the wrapped response object.
+     */
+    @Override
+    public String encodeRedirectURL(String url) {
+        return this._getHttpServletResponse().encodeRedirectURL(url);
+    }
+
+    /**
+     * The default behavior of this method is to call encodeUrl(String url) on
+     * the wrapped response object.
+     * 
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeUrl(String url) {
+        return this._getHttpServletResponse().encodeUrl(url);
+    }
+
+    /**
+     * The default behavior of this method is to return encodeRedirectUrl(String
+     * url) on the wrapped response object.
+     * 
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String encodeRedirectUrl(String url) {
+        return this._getHttpServletResponse().encodeRedirectUrl(url);
+    }
+
+    /**
+     * The default behavior of this method is to call sendError(int sc, String
+     * msg) on the wrapped response object.
+     */
+    @Override
+    public void sendError(int sc, String msg) throws IOException {
+        this._getHttpServletResponse().sendError(sc, msg);
+    }
+
+    /**
+     * The default behavior of this method is to call sendError(int sc) on the
+     * wrapped response object.
+     */
+    @Override
+    public void sendError(int sc) throws IOException {
+        this._getHttpServletResponse().sendError(sc);
+    }
+
+    /**
+     * The default behavior of this method is to return sendRedirect(String
+     * location) on the wrapped response object.
+     */
+    @Override
+    public void sendRedirect(String location) throws IOException {
+        this._getHttpServletResponse().sendRedirect(location);
+    }
+
+    /**
+     * The default behavior of this method is to call setDateHeader(String name,
+     * long date) on the wrapped response object.
+     */
+    @Override
+    public void setDateHeader(String name, long date) {
+        this._getHttpServletResponse().setDateHeader(name, date);
+    }
+
+    /**
+     * The default behavior of this method is to call addDateHeader(String name,
+     * long date) on the wrapped response object.
+     */
+    @Override
+    public void addDateHeader(String name, long date) {
+        this._getHttpServletResponse().addDateHeader(name, date);
+    }
+
+    /**
+     * The default behavior of this method is to return setHeader(String name,
+     * String value) on the wrapped response object.
+     */
+    @Override
+    public void setHeader(String name, String value) {
+        this._getHttpServletResponse().setHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to return addHeader(String name,
+     * String value) on the wrapped response object.
+     */
+    @Override
+    public void addHeader(String name, String value) {
+        this._getHttpServletResponse().addHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to call setIntHeader(String name,
+     * int value) on the wrapped response object.
+     */
+    @Override
+    public void setIntHeader(String name, int value) {
+        this._getHttpServletResponse().setIntHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to call addIntHeader(String name,
+     * int value) on the wrapped response object.
+     */
+    @Override
+    public void addIntHeader(String name, int value) {
+        this._getHttpServletResponse().addIntHeader(name, value);
+    }
+
+    /**
+     * The default behavior of this method is to call setStatus(int sc) on the
+     * wrapped response object.
+     */
+    @Override
+    public void setStatus(int sc) {
+        this._getHttpServletResponse().setStatus(sc);
+    }
+
+    /**
+     * The default behavior of this method is to call setStatus(int sc, String
+     * sm) on the wrapped response object.
+     * 
+     * @deprecated As of Version 3.0 of the Java Servlet API
+     */
+    @Override
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void setStatus(int sc, String sm) {
+        this._getHttpServletResponse().setStatus(sc, sm);
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public int getStatus() {
+        return this._getHttpServletResponse().getStatus();
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public String getHeader(String name) {
+        return this._getHttpServletResponse().getHeader(name);
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public Collection<String> getHeaders(String name) {
+        return this._getHttpServletResponse().getHeaders(name);
+    }
+
+    /**
+     * @since Servlet 3.0 TODO SERVLET3 - Add comments
+     */
+    @Override
+    public Collection<String> getHeaderNames() {
+        return this._getHttpServletResponse().getHeaderNames();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSession.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSession.java
new file mode 100644
index 0000000..60f98b1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSession.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Provides a way to identify a user across more than one page request or visit
+ * to a Web site and to store information about that user.
+ * <p>
+ * The servlet container uses this interface to create a session between an HTTP
+ * client and an HTTP server. The session persists for a specified time period,
+ * across more than one connection or page request from the user. A session
+ * usually corresponds to one user, who may visit a site many times. The server
+ * can maintain a session in many ways such as using cookies or rewriting URLs.
+ * <p>
+ * This interface allows servlets to
+ * <ul>
+ * <li>View and manipulate information about a session, such as the session
+ * identifier, creation time, and last accessed time
+ * <li>Bind objects to sessions, allowing user information to persist across
+ * multiple user connections
+ * </ul>
+ * <p>
+ * When an application stores an object in or removes an object from a session,
+ * the session checks whether the object implements
+ * {@link HttpSessionBindingListener}. If it does, the servlet notifies the
+ * object that it has been bound to or unbound from the session. Notifications
+ * are sent after the binding methods complete. For session that are invalidated
+ * or expire, notifications are sent after the session has been invalidated or
+ * expired.
+ * <p>
+ * When container migrates a session between VMs in a distributed container
+ * setting, all session attributes implementing the
+ * {@link HttpSessionActivationListener} interface are notified.
+ * <p>
+ * A servlet should be able to handle cases in which the client does not choose
+ * to join a session, such as when cookies are intentionally turned off. Until
+ * the client joins the session, <code>isNew</code> returns <code>true</code>.
+ * If the client chooses not to join the session, <code>getSession</code> will
+ * return a different session on each request, and <code>isNew</code> will
+ * always return <code>true</code>.
+ * <p>
+ * Session information is scoped only to the current web application (
+ * <code>ServletContext</code>), so information stored in one context will not
+ * be directly visible in another.
+ * 
+ * @author Various
+ * @version $Version$
+ * @see HttpSessionBindingListener
+ * @see HttpSessionContext
+ */
+public interface HttpSession {
+
+    /**
+     * Returns the time when this session was created, measured in milliseconds
+     * since midnight January 1, 1970 GMT.
+     * 
+     * @return a <code>long</code> specifying when this session was created,
+     *         expressed in milliseconds since 1/1/1970 GMT
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public long getCreationTime();
+
+    /**
+     * Returns a string containing the unique identifier assigned to this
+     * session. The identifier is assigned by the servlet container and is
+     * implementation dependent.
+     * 
+     * @return a string specifying the identifier assigned to this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public String getId();
+
+    /**
+     * Returns the last time the client sent a request associated with this
+     * session, as the number of milliseconds since midnight January 1, 1970
+     * GMT, and marked by the time the container received the request.
+     * <p>
+     * Actions that your application takes, such as getting or setting a value
+     * associated with the session, do not affect the access time.
+     * 
+     * @return a <code>long</code> representing the last time the client sent a
+     *         request associated with this session, expressed in milliseconds
+     *         since 1/1/1970 GMT
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public long getLastAccessedTime();
+
+    /**
+     * Returns the ServletContext to which this session belongs.
+     * 
+     * @return The ServletContext object for the web application
+     * @since 2.3
+     */
+    public ServletContext getServletContext();
+
+    /**
+     * Specifies the time, in seconds, between client requests before the
+     * servlet container will invalidate this session. A negative time indicates
+     * the session should never timeout.
+     * 
+     * @param interval
+     *            An integer specifying the number of seconds
+     */
+    public void setMaxInactiveInterval(int interval);
+
+    /**
+     * Returns the maximum time interval, in seconds, that the servlet container
+     * will keep this session open between client accesses. After this interval,
+     * the servlet container will invalidate the session. The maximum time
+     * interval can be set with the <code>setMaxInactiveInterval</code> method.
+     * A negative time indicates the session should never timeout.
+     * 
+     * @return an integer specifying the number of seconds this session remains
+     *         open between client requests
+     * @see #setMaxInactiveInterval
+     */
+    public int getMaxInactiveInterval();
+
+    /**
+     * @deprecated As of Version 2.1, this method is deprecated and has no
+     *             replacement. It will be removed in a future version of the
+     *             Java Servlet API.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public HttpSessionContext getSessionContext();
+
+    /**
+     * Returns the object bound with the specified name in this session, or
+     * <code>null</code> if no object is bound under the name.
+     * 
+     * @param name
+     *            a string specifying the name of the object
+     * @return the object with the specified name
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public Object getAttribute(String name);
+
+    /**
+     * @param name
+     *            a string specifying the name of the object
+     * @return the object with the specified name
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttribute}.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public Object getValue(String name);
+
+    /**
+     * Returns an <code>Enumeration</code> of <code>String</code> objects
+     * containing the names of all the objects bound to this session.
+     * 
+     * @return an <code>Enumeration</code> of <code>String</code> objects
+     *         specifying the names of all the objects bound to this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public Enumeration<String> getAttributeNames();
+
+    /**
+     * @return an array of <code>String</code> objects specifying the names of
+     *         all the objects bound to this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttributeNames}
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public String[] getValueNames();
+
+    /**
+     * Binds an object to this session, using the name specified. If an object
+     * of the same name is already bound to the session, the object is replaced.
+     * <p>
+     * After this method executes, and if the new object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>HttpSessionBindingListener.valueBound</code>. The container then
+     * notifies any <code>HttpSessionAttributeListener</code>s in the web
+     * application.
+     * <p>
+     * If an object was already bound to this session of this name that
+     * implements <code>HttpSessionBindingListener</code>, its
+     * <code>HttpSessionBindingListener.valueUnbound</code> method is called.
+     * <p>
+     * If the value passed in is null, this has the same effect as calling
+     * <code>removeAttribute()<code>.
+     * 
+     * @param name
+     *            the name to which the object is bound; cannot be null
+     * @param value
+     *            the object to be bound
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public void setAttribute(String name, Object value);
+
+    /**
+     * @param name
+     *            the name to which the object is bound; cannot be null
+     * @param value
+     *            the object to be bound; cannot be null
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #setAttribute}
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void putValue(String name, Object value);
+
+    /**
+     * Removes the object bound with the specified name from this session. If
+     * the session does not have an object bound with the specified name, this
+     * method does nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>HttpSessionBindingListener.valueUnbound</code>. The container then
+     * notifies any <code>HttpSessionAttributeListener</code>s in the web
+     * application.
+     * 
+     * @param name
+     *            the name of the object to remove from this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    public void removeAttribute(String name);
+
+    /**
+     * @param name
+     *            the name of the object to remove from this session
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #removeAttribute}
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public void removeValue(String name);
+
+    /**
+     * Invalidates this session then unbinds any objects bound to it.
+     * 
+     * @exception IllegalStateException
+     *                if this method is called on an already invalidated session
+     */
+    public void invalidate();
+
+    /**
+     * Returns <code>true</code> if the client does not yet know about the
+     * session or if the client chooses not to join the session. For example, if
+     * the server used only cookie-based sessions, and the client had disabled
+     * the use of cookies, then a session would be new on each request.
+     * 
+     * @return <code>true</code> if the server has created a session, but the
+     *         client has not yet joined
+     * @exception IllegalStateException
+     *                if this method is called on an already invalidated session
+     */
+    public boolean isNew();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionActivationListener.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionActivationListener.java
new file mode 100644
index 0000000..99a9f03
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionActivationListener.java
@@ -0,0 +1,37 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+    /** Objects that are bound to a session may listen to container
+    ** events notifying them that sessions will be passivated and that
+    ** session will be activated. A container that migrates session between VMs
+    ** or persists sessions is required to notify all attributes bound to sessions
+    ** implementing HttpSessionActivationListener.
+    **
+    * @since 2.3
+    */
+    
+public interface HttpSessionActivationListener extends EventListener { 
+
+    /** Notification that the session is about to be passivated.*/
+    public void sessionWillPassivate(HttpSessionEvent se); 
+    /** Notification that the session has just been activated.*/
+    public void sessionDidActivate(HttpSessionEvent se);
+} 
+
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionAttributeListener.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionAttributeListener.java
new file mode 100644
index 0000000..05554a1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionAttributeListener.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+/**
+ * This listener interface can be implemented in order to get notifications of
+ * changes to the attribute lists of sessions within this web application.
+ * 
+ * @since v 2.3
+ */
+public interface HttpSessionAttributeListener extends EventListener {
+
+    /**
+     * Notification that an attribute has been added to a session. Called after
+     * the attribute is added.
+     */
+    public void attributeAdded(HttpSessionBindingEvent se);
+
+    /**
+     * Notification that an attribute has been removed from a session. Called
+     * after the attribute is removed.
+     */
+    public void attributeRemoved(HttpSessionBindingEvent se);
+
+    /**
+     * Notification that an attribute has been replaced in a session. Called
+     * after the attribute is replaced.
+     */
+    public void attributeReplaced(HttpSessionBindingEvent se);
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionBindingEvent.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionBindingEvent.java
new file mode 100644
index 0000000..3501fca
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionBindingEvent.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+/**
+ * Events of this type are either sent to an object that implements
+ * {@link HttpSessionBindingListener} when it is bound or unbound from a
+ * session, or to a {@link HttpSessionAttributeListener} that has been
+ * configured in the deployment descriptor when any attribute is bound, unbound
+ * or replaced in a session.
+ * <p>
+ * The session binds the object by a call to
+ * <code>HttpSession.setAttribute</code> and unbinds the object by a call to
+ * <code>HttpSession.removeAttribute</code>.
+ * 
+ * @author Various
+ * @version $Version$
+ * @see HttpSession
+ * @see HttpSessionBindingListener
+ * @see HttpSessionAttributeListener
+ */
+public class HttpSessionBindingEvent extends HttpSessionEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    /* The name to which the object is being bound or unbound */
+    private final String name;
+
+    /* The object is being bound or unbound */
+    private final Object value;
+
+    /**
+     * Constructs an event that notifies an object that it has been bound to or
+     * unbound from a session. To receive the event, the object must implement
+     * {@link HttpSessionBindingListener}.
+     * 
+     * @param session
+     *            the session to which the object is bound or unbound
+     * @param name
+     *            the name with which the object is bound or unbound
+     * @see #getName
+     * @see #getSession
+     */
+    public HttpSessionBindingEvent(HttpSession session, String name) {
+        super(session);
+        this.name = name;
+        this.value = null;
+    }
+
+    /**
+     * Constructs an event that notifies an object that it has been bound to or
+     * unbound from a session. To receive the event, the object must implement
+     * {@link HttpSessionBindingListener}.
+     * 
+     * @param session
+     *            the session to which the object is bound or unbound
+     * @param name
+     *            the name with which the object is bound or unbound
+     * @see #getName
+     * @see #getSession
+     */
+    public HttpSessionBindingEvent(HttpSession session, String name,
+            Object value) {
+        super(session);
+        this.name = name;
+        this.value = value;
+    }
+
+    /** Return the session that changed. */
+    @Override
+    public HttpSession getSession() {
+        return super.getSession();
+    }
+
+    /**
+     * Returns the name with which the attribute is bound to or unbound from the
+     * session.
+     * 
+     * @return a string specifying the name with which the object is bound to or
+     *         unbound from the session
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the value of the attribute that has been added, removed or
+     * replaced. If the attribute was added (or bound), this is the value of the
+     * attribute. If the attribute was removed (or unbound), this is the value
+     * of the removed attribute. If the attribute was replaced, this is the old
+     * value of the attribute.
+     * 
+     * @since 2.3
+     */
+    public Object getValue() {
+        return this.value;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionBindingListener.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionBindingListener.java
new file mode 100644
index 0000000..121ca1a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionBindingListener.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+/**
+ * Causes an object to be notified when it is bound to or unbound from a
+ * session. The object is notified by an {@link HttpSessionBindingEvent} object.
+ * This may be as a result of a servlet programmer explicitly unbinding an
+ * attribute from a session, due to a session being invalidated, or due to a
+ * session timing out.
+ * 
+ * @author Various
+ * @version $Version$
+ * @see HttpSession
+ * @see HttpSessionBindingEvent
+ */
+public interface HttpSessionBindingListener extends EventListener {
+
+    /**
+     * Notifies the object that it is being bound to a session and identifies
+     * the session.
+     * 
+     * @param event
+     *            the event that identifies the session
+     * @see #valueUnbound
+     */
+    public void valueBound(HttpSessionBindingEvent event);
+
+    /**
+     * Notifies the object that it is being unbound from a session and
+     * identifies the session.
+     * 
+     * @param event
+     *            the event that identifies the session
+     * @see #valueBound
+     */
+    public void valueUnbound(HttpSessionBindingEvent event);
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionContext.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionContext.java
new file mode 100644
index 0000000..f6b2bcb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionContext.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.http;
+
+import java.util.Enumeration;
+
+/**
+ * @author Various
+ * @version $Version$
+ * @deprecated As of Java(tm) Servlet API 2.1 for security reasons, with no
+ *             replacement. This interface will be removed in a future version
+ *             of this API.
+ * @see HttpSession
+ * @see HttpSessionBindingEvent
+ * @see HttpSessionBindingListener
+ */
+@SuppressWarnings("dep-ann")
+// Spec API does not use @Deprecated
+public interface HttpSessionContext {
+
+    /**
+     * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+     *             must return null and will be removed in a future version of
+     *             this API.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public HttpSession getSession(String sessionId);
+
+    /**
+     * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+     *             must return an empty <code>Enumeration</code> and will be
+     *             removed in a future version of this API.
+     */
+    @SuppressWarnings("dep-ann")
+    // Spec API does not use @Deprecated
+    public Enumeration<String> getIds();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionEvent.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionEvent.java
new file mode 100644
index 0000000..79a34c5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionEvent.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+/**
+ * This is the class representing event notifications for changes to sessions
+ * within a web application.
+ * 
+ * @since v 2.3
+ */
+public class HttpSessionEvent extends java.util.EventObject {
+    private static final long serialVersionUID = 1L;
+
+    /** Construct a session event from the given source. */
+    public HttpSessionEvent(HttpSession source) {
+        super(source);
+    }
+
+    /** Return the session that changed. */
+    public HttpSession getSession() {
+        return (HttpSession) super.getSource();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionListener.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionListener.java
new file mode 100644
index 0000000..f7f862c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpSessionListener.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.http;
+
+import java.util.EventListener;
+
+/**
+ * Implementations of this interface are notified of changes to the list of
+ * active sessions in a web application. To receive notification events, the
+ * implementation class must be configured in the deployment descriptor for the
+ * web application.
+ * 
+ * @see HttpSessionEvent
+ * @since v 2.3
+ */
+public interface HttpSessionListener extends EventListener {
+
+    /**
+     * Notification that a session was created.
+     * 
+     * @param se
+     *            the notification event
+     */
+    public void sessionCreated(HttpSessionEvent se);
+
+    /**
+     * Notification that a session is about to be invalidated.
+     * 
+     * @param se
+     *            the notification event
+     */
+    public void sessionDestroyed(HttpSessionEvent se);
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/HttpUtils.java b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpUtils.java
new file mode 100644
index 0000000..e7e1b91
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/HttpUtils.java
@@ -0,0 +1,282 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletInputStream;
+
+/**
+ * @deprecated            As of Java(tm) Servlet API 2.3. 
+ *                        These methods were only useful
+ *                        with the default encoding and have been moved
+ *                        to the request interfaces.
+ */
+@SuppressWarnings("dep-ann") // Spec API does not use @Deprecated
+public class HttpUtils {
+
+    private static final String LSTRING_FILE =
+        "javax.servlet.http.LocalStrings";
+    private static ResourceBundle lStrings =
+        ResourceBundle.getBundle(LSTRING_FILE);
+        
+    
+    /**
+     * Constructs an empty <code>HttpUtils</code> object.
+     *
+     */
+    public HttpUtils() {
+        // NOOP
+    }
+
+
+    /**
+     *
+     * Parses a query string passed from the client to the
+     * server and builds a <code>HashTable</code> object
+     * with key-value pairs. 
+     * The query string should be in the form of a string
+     * packaged by the GET or POST method, that is, it
+     * should have key-value pairs in the form <i>key=value</i>,
+     * with each pair separated from the next by a &amp; character.
+     *
+     * <p>A key can appear more than once in the query string
+     * with different values. However, the key appears only once in 
+     * the hashtable, with its value being
+     * an array of strings containing the multiple values sent
+     * by the query string.
+     * 
+     * <p>The keys and values in the hashtable are stored in their
+     * decoded form, so
+     * any + characters are converted to spaces, and characters
+     * sent in hexadecimal notation (like <i>%xx</i>) are
+     * converted to ASCII characters.
+     *
+     * @param s                a string containing the query to be parsed
+     *
+     * @return                a <code>HashTable</code> object built
+     *                         from the parsed key-value pairs
+     *
+     * @exception IllegalArgumentException        if the query string 
+     *                                                is invalid
+     *
+     */
+    public static Hashtable<String,String[]> parseQueryString(String s) {
+
+        String valArray[] = null;
+        
+        if (s == null) {
+            throw new IllegalArgumentException();
+        }
+        Hashtable<String,String[]> ht = new Hashtable<String,String[]>();
+        StringBuilder sb = new StringBuilder();
+        StringTokenizer st = new StringTokenizer(s, "&");
+        while (st.hasMoreTokens()) {
+            String pair = st.nextToken();
+            int pos = pair.indexOf('=');
+            if (pos == -1) {
+                // XXX
+                // should give more detail about the illegal argument
+                throw new IllegalArgumentException();
+            }
+            String key = parseName(pair.substring(0, pos), sb);
+            String val = parseName(pair.substring(pos+1, pair.length()), sb);
+            if (ht.containsKey(key)) {
+                String oldVals[] = ht.get(key);
+                valArray = new String[oldVals.length + 1];
+                for (int i = 0; i < oldVals.length; i++) 
+                    valArray[i] = oldVals[i];
+                valArray[oldVals.length] = val;
+            } else {
+                valArray = new String[1];
+                valArray[0] = val;
+            }
+            ht.put(key, valArray);
+        }
+        return ht;
+    }
+
+
+    /**
+     *
+     * Parses data from an HTML form that the client sends to 
+     * the server using the HTTP POST method and the 
+     * <i>application/x-www-form-urlencoded</i> MIME type.
+     *
+     * <p>The data sent by the POST method contains key-value
+     * pairs. A key can appear more than once in the POST data
+     * with different values. However, the key appears only once in 
+     * the hashtable, with its value being
+     * an array of strings containing the multiple values sent
+     * by the POST method.
+     *
+     * <p>The keys and values in the hashtable are stored in their
+     * decoded form, so
+     * any + characters are converted to spaces, and characters
+     * sent in hexadecimal notation (like <i>%xx</i>) are
+     * converted to ASCII characters.
+     *
+     *
+     *
+     * @param len        an integer specifying the length,
+     *                        in characters, of the 
+     *                        <code>ServletInputStream</code>
+     *                        object that is also passed to this
+     *                        method
+     *
+     * @param in        the <code>ServletInputStream</code>
+     *                        object that contains the data sent
+     *                        from the client
+     * 
+     * @return                a <code>HashTable</code> object built
+     *                        from the parsed key-value pairs
+     *
+     *
+     * @exception IllegalArgumentException        if the data
+     *                        sent by the POST method is invalid
+     *
+     */
+    public static Hashtable<String,String[]> parsePostData(int len, 
+                                          ServletInputStream in) {
+        // XXX
+        // should a length of 0 be an IllegalArgumentException
+        
+        // cheap hack to return an empty hash
+        if (len <=0) 
+            return new Hashtable<String,String[]>();
+
+        if (in == null) {
+            throw new IllegalArgumentException();
+        }
+        
+        // Make sure we read the entire POSTed body.
+        byte[] postedBytes = new byte [len];
+        try {
+            int offset = 0;
+       
+            do {
+                int inputLen = in.read (postedBytes, offset, len - offset);
+                if (inputLen <= 0) {
+                    String msg = lStrings.getString("err.io.short_read");
+                    throw new IllegalArgumentException (msg);
+                }
+                offset += inputLen;
+            } while ((len - offset) > 0);
+
+        } catch (IOException e) {
+            throw new IllegalArgumentException(e.getMessage(), e);
+        }
+
+        // XXX we shouldn't assume that the only kind of POST body
+        // is FORM data encoded using ASCII or ISO Latin/1 ... or
+        // that the body should always be treated as FORM data.
+        try {
+            String postedBody = new String(postedBytes, 0, len, "8859_1");
+            return parseQueryString(postedBody);
+        } catch (java.io.UnsupportedEncodingException e) {
+            // XXX function should accept an encoding parameter & throw this
+            // exception.  Otherwise throw something expected.
+            throw new IllegalArgumentException(e.getMessage(), e);
+        }
+    }
+
+
+    /*
+     * Parse a name in the query string.
+     */
+    private static String parseName(String s, StringBuilder sb) {
+        sb.setLength(0);
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i); 
+            switch (c) {
+            case '+':
+                sb.append(' ');
+                break;
+            case '%':
+                try {
+                    sb.append((char) Integer.parseInt(s.substring(i+1, i+3), 
+                                                      16));
+                    i += 2;
+                } catch (NumberFormatException e) {
+                    // XXX
+                    // need to be more specific about illegal arg
+                    throw new IllegalArgumentException();
+                } catch (StringIndexOutOfBoundsException e) {
+                    String rest  = s.substring(i);
+                    sb.append(rest);
+                    if (rest.length()==2)
+                        i++;
+                }
+                
+                break;
+            default:
+                sb.append(c);
+                break;
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     *
+     * Reconstructs the URL the client used to make the request,
+     * using information in the <code>HttpServletRequest</code> object.
+     * The returned URL contains a protocol, server name, port
+     * number, and server path, but it does not include query
+     * string parameters.
+     * 
+     * <p>Because this method returns a <code>StringBuffer</code>,
+     * not a string, you can modify the URL easily, for example,
+     * to append query parameters.
+     *
+     * <p>This method is useful for creating redirect messages
+     * and for reporting errors.
+     *
+     * @param req        a <code>HttpServletRequest</code> object
+     *                        containing the client's request
+     * 
+     * @return                a <code>StringBuffer</code> object containing
+     *                        the reconstructed URL
+     *
+     */
+    public static StringBuffer getRequestURL (HttpServletRequest req) {
+        StringBuffer url = new StringBuffer ();
+        String scheme = req.getScheme ();
+        int port = req.getServerPort ();
+        String urlPath = req.getRequestURI();
+        
+        url.append (scheme);                // http, https
+        url.append ("://");
+        url.append (req.getServerName ());
+        if ((scheme.equals ("http") && port != 80)
+                || (scheme.equals ("https") && port != 443)) {
+            url.append (':');
+            url.append (req.getServerPort ());
+        }
+
+        url.append(urlPath);
+        return url;
+    }
+}
+
+
+
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings.properties b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings.properties
new file mode 100644
index 0000000..7621fee
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings.properties
@@ -0,0 +1,29 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale en_US
+
+err.cookie_name_is_token=Cookie name \"{0}\" is a reserved token
+err.cookie_name_blank=Cookie name may not be null or zero length
+err.io.negativelength=Negative Length given in write method
+err.io.short_read=Short Read
+
+http.method_not_implemented=Method {0} is not is not implemented by this servlet for this URI 
+
+http.method_get_not_supported=HTTP method GET is not supported by this URL
+http.method_post_not_supported=HTTP method POST is not supported by this URL
+http.method_put_not_supported=HTTP method PUT is not supported by this URL
+http.method_delete_not_supported=Http method DELETE is not supported by this URL
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_es.properties
new file mode 100644
index 0000000..793fa6b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_es.properties
@@ -0,0 +1,30 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# $Id: LocalStrings_es.properties,v 1.1 2011/06/28 21:14:12 rherrmann Exp $
+#
+# Default localized string information
+# Localized para Locale es_ES
+
+err.cookie_name_is_token=El Nombre de Cookie {0} es una palabra reservada
+err.io.negativelength=Longitud Negativa en el metodo write
+err.io.short_read=Lectura Corta
+
+http.method_not_implemented=El Metodo {0} no esta definido en la especificacion RFC 2068 y no es soportado por la API Servlet 
+
+http.method_get_not_supported=El Metodo HTTP GET no es soportado por esta URL
+http.method_post_not_supported=El Metodo HTTP POST no es soportado por esta URL
+http.method_put_not_supported=El Metodo HTTP PUT no es soportado por esta URL
+http.method_delete_not_supported=El Metodo HTTP DELETE no es soportado por esta URL
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_fr.properties
new file mode 100644
index 0000000..47b8dd7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_fr.properties
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale fr_FR
+
+err.cookie_name_is_token=Le nom de cookie \"{0}\" est un \"token\" r\u00e9serv\u00e9
+err.io.negativelength=Taille n\u00e9gative donn\u00e9e dans la m\u00e9thode \"write\"
+err.io.short_read=Lecture partielle
+
+http.method_not_implemented=Le m\u00e9thode {0} n''est pas d\u00e9finie dans la RFC 2068 et n''est pas support\u00e9e par l''API Servlet
+
+http.method_get_not_supported=La m\u00e9thode HTTP GET n''est pas support\u00e9e par cette URL
+http.method_post_not_supported=La m\u00e9thode HTTP POST n''est pas support\u00e9e par cette URL
+http.method_put_not_supported=La m\u00e9thode HTTP PUT n''est pas support\u00e9e par cette URL
+http.method_delete_not_supported=La m\u00e9thode HTTP DELETE n''est pas support\u00e9e par cette URL 
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_ja.properties
new file mode 100644
index 0000000..70983d7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/LocalStrings_ja.properties
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale ja_JP
+
+err.cookie_name_is_token=\u30af\u30c3\u30ad\u30fc\u540d \"{0}\" \u306f\u4e88\u7d04\u6e08\u306e\u30c8\u30fc\u30af\u30f3\u3067\u3059\u3002
+err.io.negativelength=write\u30e1\u30bd\u30c3\u30c9\u306b\u8ca0\u306e\u9577\u3055\u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f\u3002
+err.io.short_read=\u8aad\u307f\u8fbc\u307f\u304c\u3059\u3050\u306b\u7d42\u308f\u308a\u307e\u3057\u305f\u3002
+
+http.method_not_implemented=\u30e1\u30bd\u30c3\u30c9 {0} \u306fRFC 2068\u306b\u306f\u5b9a\u7fa9\u3055\u308c\u3066\u304a\u3089\u305a\u3001\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8API\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u307e\u305b\u3093\u3002
+
+http.method_get_not_supported=HTTP\u306eGET\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+http.method_post_not_supported=HTTP\u306ePOST\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+http.method_put_not_supported=HTTP\u306ePUT\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+http.method_delete_not_supported=HTTP\u306eDELETE\u30e1\u30bd\u30c3\u30c9\u306f\u3001\u3053\u306eURL\u3067\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/Part.java b/bundles/org.apache.tomcat/src/javax/servlet/http/Part.java
new file mode 100644
index 0000000..3e60e40
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/Part.java
@@ -0,0 +1,44 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+
+/**
+ * @since Servlet 3.0
+ * TODO SERVLET3 - Add comments
+ */
+public interface Part {
+    public InputStream getInputStream() throws IOException;
+    public String getContentType();
+    public String getName();
+    public long getSize();
+    public void write(String fileName) throws IOException;
+    public void delete() throws IOException;
+    
+    /**
+     * Obtains the value of the specified mime header for the part.
+     * @param name Header name
+     * @return The header value or <code>null</code> if the header is not
+     *         present
+     */
+    public String getHeader(String name);
+    public Collection<String> getHeaders(String name);
+    public Collection<String> getHeaderNames();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/http/package.html b/bundles/org.apache.tomcat/src/javax/servlet/http/package.html
new file mode 100644
index 0000000..08e6692
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/http/package.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+</HEAD>
+<BODY BGCOLOR="white">
+
+The javax.servlet.http package contains a number of classes and interfaces
+that describe and define the contracts between a servlet class
+running under the HTTP protocol and the runtime environment provided
+for an instance of such a class by a conforming servlet container.
+
+
+</BODY>
+</HTML>
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/ErrorData.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/ErrorData.java
new file mode 100644
index 0000000..a517ad7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/ErrorData.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.jsp;
+
+/**
+ * Contains information about an error, for error pages. The information
+ * contained in this instance is meaningless if not used in the context of an
+ * error page. To indicate a JSP is an error page, the page author must set the
+ * isErrorPage attribute of the page directive to "true".
+ * 
+ * @see PageContext#getErrorData
+ * @since 2.0
+ */
+public final class ErrorData {
+
+    private final Throwable throwable;
+    private final int statusCode;
+    private final String uri;
+    private final String servletName;
+
+    /**
+     * Creates a new ErrorData object.
+     * 
+     * @param throwable
+     *            The Throwable that is the cause of the error
+     * @param statusCode
+     *            The status code of the error
+     * @param uri
+     *            The request URI
+     * @param servletName
+     *            The name of the servlet invoked
+     */
+    public ErrorData(Throwable throwable, int statusCode, String uri,
+            String servletName) {
+        this.throwable = throwable;
+        this.statusCode = statusCode;
+        this.uri = uri;
+        this.servletName = servletName;
+    }
+
+    /**
+     * Returns the Throwable that caused the error.
+     * 
+     * @return The Throwable that caused the error
+     */
+    public Throwable getThrowable() {
+        return this.throwable;
+    }
+
+    /**
+     * Returns the status code of the error.
+     * 
+     * @return The status code of the error
+     */
+    public int getStatusCode() {
+        return this.statusCode;
+    }
+
+    /**
+     * Returns the request URI.
+     * 
+     * @return The request URI
+     */
+    public String getRequestURI() {
+        return this.uri;
+    }
+
+    /**
+     * Returns the name of the servlet invoked.
+     * 
+     * @return The name of the servlet invoked
+     */
+    public String getServletName() {
+        return this.servletName;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/HttpJspPage.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/HttpJspPage.java
new file mode 100644
index 0000000..0351a09
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/HttpJspPage.java
@@ -0,0 +1,60 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ 
+package javax.servlet.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The HttpJspPage interface describes the interaction that a JSP Page
+ * Implementation Class must satisfy when using the HTTP protocol.
+ *
+ * <p>
+ * The behaviour is identical to that of the JspPage, except for the signature
+ * of the _jspService method, which is now expressible in the Java type
+ * system and included explicitly in the interface.
+ * 
+ * @see JspPage
+ */
+
+public interface HttpJspPage extends JspPage {
+
+    /** The _jspService()method corresponds to the body of the JSP page. This
+     * method is defined automatically by the JSP container and should never
+     * be defined by the JSP page author.
+     * <p>
+     * If a superclass is specified using the extends attribute, that
+     * superclass may choose to perform some actions in its service() method
+     * before or after calling the _jspService() method.  See using the extends
+     * attribute in the JSP_Engine chapter of the JSP specification.
+     *
+     * @param request Provides client request information to the JSP.
+     * @param response Assists the JSP in sending a response to the client.
+     * @throws ServletException Thrown if an error occurred during the 
+     *     processing of the JSP and that the container should take 
+     *     appropriate action to clean up the request.
+     * @throws IOException Thrown if an error occurred while writing the
+     *     response for this page.
+     */
+    public void _jspService(HttpServletRequest request,
+                            HttpServletResponse response)
+       throws ServletException, IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspApplicationContext.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspApplicationContext.java
new file mode 100644
index 0000000..10817e0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspApplicationContext.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.jsp;
+
+import javax.el.ELContextListener;
+import javax.el.ELResolver;
+import javax.el.ExpressionFactory;
+
+/**
+ * <p>
+ * Stores <i>application</i>-scoped information for the JSP container.
+ * </p>
+ * 
+ * @since 2.1
+ */
+public interface JspApplicationContext {
+
+    /**
+     * <p>
+     * Registers an <code>ELContextListener</code> that will notified whenever a
+     * new <code>ELContext</code> is created.
+     * </p>
+     * <p>
+     * At the very least, any <code>ELContext</code> instantiated will have
+     * reference to the <code>JspContext</code> under
+     * <code>JspContext.class</code>.
+     * </p>
+     * 
+     * @param listener
+     */
+    public void addELContextListener(ELContextListener listener);
+
+    /**
+     * <p>
+     * Adds an <code>ELResolver</code> to the chain of EL variable and property
+     * management within JSP pages and Tag files.
+     * </p>
+     * <p>
+     * JSP has a default set of ELResolvers to chain for all EL evaluation:
+     * <ul>
+     * <li><code>ImplicitObjectELResolver</code></li>
+     * <li><code>ELResolver</code> instances registered with this method</li>
+     * <li><code>MapELResolver</code></li>
+     * <li><code>ListELResolver</code></li>
+     * <li><code>ArrayELResolver</code></li>
+     * <li><code>BeanELResolver</code></li>
+     * <li><code>ScopedAttributeELResolver</code></li>
+     * </ul>
+     * </p>
+     * 
+     * @param resolver
+     *            an additional resolver
+     * @throws IllegalStateException
+     *             if called after the application's
+     *             <code>ServletContextListeners</code> have been initialized.
+     */
+    public void addELResolver(ELResolver resolver) throws IllegalStateException;
+
+    /**
+     * <p>
+     * Returns the JSP container's <code>ExpressionFactory</code> implementation
+     * for EL use.
+     * </p>
+     * 
+     * @return an <code>ExpressionFactory</code> implementation
+     */
+    public ExpressionFactory getExpressionFactory();
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspContext.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspContext.java
new file mode 100644
index 0000000..3898319
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspContext.java
@@ -0,0 +1,282 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/ 
+package javax.servlet.jsp;
+
+import java.util.Enumeration;
+
+import javax.el.ELContext;
+
+/**
+ * <p>
+ * <code>JspContext</code> serves as the base class for the 
+ * PageContext class and abstracts all information that is not specific
+ * to servlets.  This allows for Simple Tag Extensions to be used
+ * outside of the context of a request/response Servlet.
+ * <p>
+ * The JspContext provides a number of facilities to the 
+ * page/component author and page implementor, including:
+ * <ul>
+ * <li>a single API to manage the various scoped namespaces
+ * <li>a mechanism to obtain the JspWriter for output
+ * <li>a mechanism to expose page directive attributes to the 
+ *     scripting environment
+ * </ul>
+ *
+ * <p><B>Methods Intended for Container Generated Code</B>
+ * <p>
+ * The following methods enable the <B>management of nested</B> JspWriter 
+ * streams to implement Tag Extensions: <code>pushBody()</code> and
+ * <code>popBody()</code>
+ *
+ * <p><B>Methods Intended for JSP authors</B>
+ * <p>
+ * Some methods provide <B>uniform access</B> to the diverse objects
+ * representing scopes.
+ * The implementation must use the underlying machinery
+ * corresponding to that scope, so information can be passed back and
+ * forth between the underlying environment (e.g. Servlets) and JSP pages.
+ * The methods are:
+ * <code>setAttribute()</code>,  <code>getAttribute()</code>,
+ * <code>findAttribute()</code>,  <code>removeAttribute()</code>,
+ * <code>getAttributesScope()</code> and 
+ * <code>getAttributeNamesInScope()</code>.
+ * 
+ * <p>
+ * The following methods provide <B>convenient access</B> to implicit objects:
+ * <code>getOut()</code>
+ *
+ * <p>
+ * The following methods provide <B>programmatic access</b> to the 
+ * Expression Language evaluator:
+ * <code>getExpressionEvaluator()</code>, <code>getVariableResolver()</code>
+ *
+ * @since 2.0
+ */
+
+public abstract class JspContext {
+
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public JspContext() {
+        // NOOP by default
+    }
+    
+    /** 
+     * Register the name and value specified with page scope semantics.
+     * If the value passed in is <code>null</code>, this has the same 
+     * effect as calling 
+     * <code>removeAttribute( name, PageContext.PAGE_SCOPE )</code>.
+     *
+     * @param name the name of the attribute to set
+     * @param value the value to associate with the name, or null if the
+     *     attribute is to be removed from the page scope.
+     * @throws NullPointerException if the name is null
+     */
+
+    public abstract void setAttribute(String name, Object value);
+
+    /**
+     * Register the name and value specified with appropriate 
+     * scope semantics.  If the value passed in is <code>null</code>, 
+     * this has the same effect as calling
+     * <code>removeAttribute( name, scope )</code>.
+     * 
+     * @param name the name of the attribute to set
+     * @param value the object to associate with the name, or null if
+     *     the attribute is to be removed from the specified scope.
+     * @param scope the scope with which to associate the name/object
+     * 
+     * @throws NullPointerException if the name is null
+     * @throws IllegalArgumentException if the scope is invalid
+     * @throws IllegalStateException if the scope is 
+     *     PageContext.SESSION_SCOPE but the page that was requested
+     *     does not participate in a session or the session has been
+     *     invalidated.
+     */
+
+    public abstract void setAttribute(String name, Object value, int scope);
+
+    /**
+     * Returns the object associated with the name in the page scope or null
+     * if not found.
+     *
+     * @param name the name of the attribute to get
+     * @return the object associated with the name in the page scope 
+     *     or null if not found.
+     * 
+     * @throws NullPointerException if the name is null
+     */
+
+    public abstract Object getAttribute(String name);
+
+    /**
+     * Return the object associated with the name in the specified
+     * scope or null if not found.
+     *
+     * @param name the name of the attribute to set
+     * @param scope the scope with which to associate the name/object
+     * @return the object associated with the name in the specified
+     *     scope or null if not found.
+     * 
+     * @throws NullPointerException if the name is null
+     * @throws IllegalArgumentException if the scope is invalid 
+     * @throws IllegalStateException if the scope is 
+     *     PageContext.SESSION_SCOPE but the page that was requested
+     *     does not participate in a session or the session has been
+     *     invalidated.
+     */
+
+    public abstract Object getAttribute(String name, int scope);
+
+    /**
+     * Searches for the named attribute in page, request, session (if valid),
+     * and application scope(s) in order and returns the value associated or
+     * null.
+     *
+     * @param name the name of the attribute to search for
+     * @return the value associated or null
+     * @throws NullPointerException if the name is null
+     */
+
+    public abstract Object findAttribute(String name);
+
+    /**
+     * Remove the object reference associated with the given name
+     * from all scopes.  Does nothing if there is no such object.
+     *
+     * @param name The name of the object to remove.
+     * @throws NullPointerException if the name is null
+     */
+
+    public abstract void removeAttribute(String name);
+
+    /**
+     * Remove the object reference associated with the specified name
+     * in the given scope.  Does nothing if there is no such object.
+     *
+     * @param name The name of the object to remove.
+     * @param scope The scope where to look.
+     * @throws IllegalArgumentException if the scope is invalid
+     * @throws IllegalStateException if the scope is 
+     *     PageContext.SESSION_SCOPE but the page that was requested
+     *     does not participate in a session or the session has been
+     *     invalidated.
+     * @throws NullPointerException if the name is null
+     */
+
+    public abstract void removeAttribute(String name, int scope);
+
+    /**
+     * Get the scope where a given attribute is defined.
+     *
+     * @param name the name of the attribute to return the scope for
+     * @return the scope of the object associated with the name specified or 0
+     * @throws NullPointerException if the name is null
+     */
+
+    public abstract int getAttributesScope(String name);
+
+    /**
+     * Enumerate all the attributes in a given scope.
+     *
+     * @param scope the scope to enumerate all the attributes for
+     * @return an enumeration of names (java.lang.String) of all the 
+     *     attributes the specified scope
+     * @throws IllegalArgumentException if the scope is invalid
+     * @throws IllegalStateException if the scope is 
+     *     PageContext.SESSION_SCOPE but the page that was requested
+     *     does not participate in a session or the session has been
+     *     invalidated.
+     */
+
+    public abstract Enumeration<String> getAttributeNamesInScope(int scope);
+
+    /**
+     * The current value of the out object (a JspWriter).
+     *
+     * @return the current JspWriter stream being used for client response
+     */
+    public abstract JspWriter getOut();
+    
+    /**
+     * Provides programmatic access to the ExpressionEvaluator.
+     * The JSP Container must return a valid instance of an 
+     * ExpressionEvaluator that can parse EL expressions.
+     *
+     * @return A valid instance of an ExpressionEvaluator.
+     * @since 2.0
+     * @deprecated As of JSP 2.1, replaced by
+     *             JspApplicationContext.getExpressionFactory()
+     */
+    @SuppressWarnings("dep-ann") // TCK signature test fails with annotation
+    public abstract javax.servlet.jsp.el.ExpressionEvaluator getExpressionEvaluator();
+    
+    
+    public abstract ELContext getELContext();
+    
+    /**
+     * Returns an instance of a VariableResolver that provides access to the
+     * implicit objects specified in the JSP specification using this JspContext
+     * as the context object.
+     *
+     * @return A valid instance of a VariableResolver.
+     * @since 2.0
+     * @deprecated As of JSP 2.1,
+     *             replaced by javax.el.ELContext.getELResolver()
+     *             which can be obtained by
+     *             jspContext.getELContext().getELResolver()
+     */
+    @SuppressWarnings("dep-ann") // TCK signature test fails with annotation
+    public abstract javax.servlet.jsp.el.VariableResolver getVariableResolver();
+    
+    /**
+     * Return a new JspWriter object that sends output to the
+     * provided Writer.  Saves the current "out" JspWriter,
+     * and updates the value of the "out" attribute in the
+     * page scope attribute namespace of the JspContext.
+     * <p>The returned JspWriter must implement all methods and
+     * behave as though it were unbuffered.  More specifically:
+     * <ul>
+     *   <li>clear() must throw an IOException</li>
+     *   <li>clearBuffer() does nothing</li>
+     *   <li>getBufferSize() always returns 0</li>
+     *   <li>getRemaining() always returns 0</li>
+     * </ul>
+     * </p>
+     *
+     * @param writer The Writer for the returned JspWriter to send
+     *     output to.
+     * @return a new JspWriter that writes to the given Writer.
+     * @since 2.0
+     */
+    public JspWriter pushBody( java.io.Writer writer ) {
+        return null; // XXX to implement
+    }
+    
+    /**
+     * Return the previous JspWriter "out" saved by the matching
+     * pushBody(), and update the value of the "out" attribute in
+     * the page scope attribute namespace of the JspContext.
+     *
+     * @return the saved JspWriter.
+     */
+    public JspWriter popBody() {
+        return null; // XXX to implement
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspEngineInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspEngineInfo.java
new file mode 100644
index 0000000..b07a0a3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspEngineInfo.java
@@ -0,0 +1,49 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/ 
+package javax.servlet.jsp;
+
+/**
+ * The JspEngineInfo is an abstract class that provides information on the
+ * current JSP engine.
+ */
+
+public abstract class JspEngineInfo {
+
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public JspEngineInfo() {
+        // NOOP by default
+    }
+    
+    /**
+     * Return the version number of the JSP specification that is supported by
+     * this JSP engine.
+     * <p>
+     * Specification version numbers that consists of positive decimal integers
+     * separated by periods ".", for example, "2.0" or "1.2.3.4.5.6.7".
+     * This allows an extensible number to be used to
+     * represent major, minor, micro, etc versions.
+     * The version number must begin with a number.
+     * </p>
+     *
+     * @return the specification version, null is returned if it is not known
+     */
+
+    public abstract String getSpecificationVersion();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspException.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspException.java
new file mode 100644
index 0000000..90f311a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspException.java
@@ -0,0 +1,101 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet.jsp;
+
+/**
+ * A generic exception known to the JSP engine; uncaught
+ * JspExceptions will result in an invocation of the errorpage
+ * machinery.
+ */
+public class JspException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+
+    /**
+     * Construct a JspException.
+     */
+    public JspException() {
+        // NOOP
+    }
+
+
+    /**
+     * Constructs a new JSP exception with the
+     * specified message. The message can be written 
+     * to the server log and/or displayed for the user. 
+     *
+     * @param msg   a <code>String</code> specifying the text of the exception
+     *              message
+     */
+    public JspException(String msg) {
+        super(msg);
+    }
+
+
+    /**
+     * Constructs a new <code>JSPException</code> with the specified detail
+     * message and cause. The cause is saved for later retrieval by the
+     * <code>java.lang.Throwable.getCause()</code> and {@link #getRootCause()}
+     * methods.
+     * 
+     * @see <code>java.lang.Exception.Exception(String, Throwable)</code>
+     *
+     * @param message       a <code>String</code> containing the text of the
+     *                      exception message
+     *
+     * @param cause         the <code>Throwable</code> exception that
+     *                      interfered with the JSP's normal operation,
+     *                      making this JSP exception necessary
+     */
+    
+    public JspException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+
+    /**
+     * Constructs a new <code>JSPException</code> with the specified cause.
+     * The cause is saved for later retrieval by the
+     * <code>java.lang.Throwable.getCause()</code> and {@link #getRootCause()}
+     * methods.
+     * 
+     * @see <code>java.lang.Exception.Exception(Throwable)</code>
+     *
+     * @param cause         the <code>Throwable</code> exception that
+     *                      interfered with the JSP's normal operation, making
+     *                      the JSP exception necessary
+     */
+
+    public JspException(Throwable cause) {
+        super(cause);
+    }
+
+    
+    /**
+     * Returns the exception that caused this JSP exception.
+     *
+     * @return  the <code>Throwable</code> that caused this JSP exception
+     * 
+     * @deprecated As of JSP 2.1, replaced by
+     * <code>java.lang.Throwable.getCause()</code>
+     */
+    @SuppressWarnings("dep-ann") // TCK signature test fails with annotation
+    public Throwable getRootCause() {
+        return getCause();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspFactory.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspFactory.java
new file mode 100644
index 0000000..278c528
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspFactory.java
@@ -0,0 +1,157 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package javax.servlet.jsp;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * <p>
+ * The JspFactory is an abstract class that defines a number of factory
+ * methods available to a JSP page at runtime for the purposes of creating
+ * instances of various interfaces and classes used to support the JSP 
+ * implementation.
+ * <p>
+ * A conformant JSP Engine implementation will, during it's initialization
+ * instantiate an implementation dependent subclass of this class, and make 
+ * it globally available for use by JSP implementation classes by registering
+ * the instance created with this class via the
+ * static <code> setDefaultFactory() </code> method.
+ * <p>
+ * The PageContext and the JspEngineInfo classes are the only
+ * implementation-dependent classes that can be created from the factory.
+ * <p>
+ * JspFactory objects should not be used by JSP page authors.
+ */
+
+public abstract class JspFactory {
+
+    private static volatile JspFactory deflt = null;
+    
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public JspFactory() {
+        // NOOP by default
+    }
+
+    /**
+     * <p>
+     * set the default factory for this implementation. It is illegal for
+     * any principal other than the JSP Engine runtime to call this method.
+     * </p>
+     *
+     * @param deflt        The default factory implementation
+     */
+
+    public static void setDefaultFactory(JspFactory deflt) {
+        JspFactory.deflt = deflt;
+    }
+
+    /**
+     * Returns the default factory for this implementation.
+     *
+     * @return the default factory for this implementation
+     */
+
+    public static JspFactory getDefaultFactory() {
+        return deflt;
+    }
+
+    /**
+     * <p>
+     * obtains an instance of an implementation dependent 
+     * javax.servlet.jsp.PageContext abstract class for the calling Servlet
+     * and currently pending request and response.
+     * </p>
+     *
+     * <p>
+     * This method is typically called early in the processing of the 
+     * _jspService() method of a JSP implementation class in order to 
+     * obtain a PageContext object for the request being processed.
+     * </p>
+     * <p>
+     * Invoking this method shall result in the PageContext.initialize()
+     * method being invoked. The PageContext returned is properly initialized.
+     * </p>
+     * <p>
+     * All PageContext objects obtained via this method shall be released
+     * by invoking releasePageContext().
+     * </p>
+     *
+     * @param servlet      the requesting servlet
+     * @param request      the current request pending on the servlet
+     * @param response     the current response pending on the servlet
+     * @param errorPageURL the URL of the error page for the requesting JSP, or
+     *                         null
+     * @param needsSession true if the JSP participates in a session
+     * @param buffer       size of buffer in bytes, PageContext.NO_BUFFER if no
+     *                         buffer, PageContext.DEFAULT_BUFFER if
+     *                         implementation default.
+     * @param autoflush    should the buffer autoflush to the output stream on
+     *                         buffer overflow, or throw an IOException?
+     *
+     * @return the page context
+     *
+     * @see javax.servlet.jsp.PageContext
+     */
+
+    public abstract PageContext getPageContext(Servlet servlet,
+            ServletRequest request, ServletResponse response,
+            String errorPageURL, boolean needsSession, int buffer,
+            boolean autoflush);
+
+    /**
+     * <p>
+     * called to release a previously allocated PageContext object.
+     * Results in PageContext.release() being invoked.
+     * This method should be invoked prior to returning from the _jspService()
+     * method of a JSP implementation class.
+     * </p>
+     *
+     * @param pc A PageContext previously obtained by getPageContext()
+     */
+    public abstract void releasePageContext(PageContext pc);
+
+    /**
+     * <p>
+     * called to get implementation-specific information on the current JSP
+     * engine.
+     * </p>
+     *
+     * @return a JspEngineInfo object describing the current JSP engine
+     */
+    
+    public abstract JspEngineInfo getEngineInfo();
+    
+    /**
+     * <p>
+     * Obtain the <code>JspApplicationContext</code> instance that was
+     * associated within the passed <code>ServletContext</code> for this web
+     * application.
+     * </p>
+     * 
+     * @param context the current web application's <code>ServletContext</code>
+     * @return <code>JspApplicationContext</code> instance
+     * @since 2.1
+     */
+    public abstract JspApplicationContext getJspApplicationContext(
+            ServletContext context);
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspPage.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspPage.java
new file mode 100644
index 0000000..bcc9a92
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspPage.java
@@ -0,0 +1,90 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/ 
+package javax.servlet.jsp;
+
+import javax.servlet.Servlet;
+
+/**
+ * The JspPage interface describes the generic interaction that a JSP Page
+ * Implementation class must satisfy; pages that use the HTTP protocol
+ * are described by the HttpJspPage interface.
+ *
+ * <p><B>Two plus One Methods</B>
+ * <p>
+ * The interface defines a protocol with 3 methods; only two of
+ * them: jspInit() and jspDestroy() are part of this interface as
+ * the signature of the third method: _jspService() depends on
+ * the specific protocol used and cannot be expressed in a generic
+ * way in Java.
+ * <p>
+ * A class implementing this interface is responsible for invoking
+ * the above methods at the appropriate time based on the
+ * corresponding Servlet-based method invocations.
+ * <p>
+ * The jspInit() and jspDestroy() methods can be defined by a JSP
+ * author, but the _jspService() method is defined automatically
+ * by the JSP processor based on the contents of the JSP page.
+ *
+ * <p><B>_jspService()</B>
+ * <p>
+ * The _jspService()method corresponds to the body of the JSP page. This
+ * method is defined automatically by the JSP container and should never
+ * be defined by the JSP page author.
+ * <p>
+ * If a superclass is specified using the extends attribute, that
+ * superclass may choose to perform some actions in its service() method
+ * before or after calling the _jspService() method.  See using the extends
+ * attribute in the JSP_Engine chapter of the JSP specification.
+ * <p>
+ * The specific signature depends on the protocol supported by the JSP page.
+ *
+ * <pre>
+ * public void _jspService(<em>ServletRequestSubtype</em> request,
+ *                             <em>ServletResponseSubtype</em> response)
+ *        throws ServletException, IOException;
+ * </pre>
+ */
+
+
+public interface JspPage extends Servlet {
+
+    /**
+     * The jspInit() method is invoked when the JSP page is initialized. It
+     * is the responsibility of the JSP implementation (and of the class
+     * mentioned by the extends attribute, if present) that at this point
+     * invocations to the getServletConfig() method will return the desired
+     * value.
+     *
+     * A JSP page can override this method by including a definition for it
+     * in a declaration element.
+     *
+     * A JSP page should redefine the init() method from Servlet.
+     */
+    public void jspInit();
+
+    /**
+     * The jspDestroy() method is invoked when the JSP page is about to be
+     * destroyed.
+     * 
+     * A JSP page can override this method by including a definition for it
+     * in a declaration element.
+     *
+     * A JSP page should redefine the destroy() method from Servlet.
+     */
+    public void jspDestroy();
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspTagException.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspTagException.java
new file mode 100644
index 0000000..64d8697
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspTagException.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.jsp;
+
+/**
+ * Exception to be used by a Tag Handler to indicate some unrecoverable error.
+ * This error is to be caught by the top level of the JSP page and will result
+ * in an error page.
+ */
+public class JspTagException extends JspException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new JspTagException with the specified message. The message
+     * can be written to the server log and/or displayed for the user.
+     * 
+     * @param msg
+     *            a <code>String</code> specifying the text of the exception
+     *            message
+     */
+    public JspTagException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * Constructs a new JspTagException with no message.
+     */
+    public JspTagException() {
+        super();
+    }
+
+    /**
+     * Constructs a new JspTagException when the JSP Tag needs to throw an
+     * exception and include a message about the "root cause" exception that
+     * interfered with its normal operation, including a description message.
+     * 
+     * @param message
+     *            a <code>String</code> containing the text of the exception
+     *            message
+     * @param rootCause
+     *            the <code>Throwable</code> exception that interfered with the
+     *            JSP Tag's normal operation, making this JSP Tag exception
+     *            necessary
+     * @since 2.0
+     */
+    public JspTagException(String message, Throwable rootCause) {
+        super(message, rootCause);
+    }
+
+    /**
+     * Constructs a new JSP Tag exception when the JSP Tag needs to throw an
+     * exception and include a message about the "root cause" exception that
+     * interfered with its normal operation. The exception's message is based on
+     * the localized message of the underlying exception.
+     * <p>
+     * This method calls the <code>getLocalizedMessage</code> method on the
+     * <code>Throwable</code> exception to get a localized exception message.
+     * When subclassing <code>JspTagException</code>, this method can be
+     * overridden to create an exception message designed for a specific locale.
+     * 
+     * @param rootCause
+     *            the <code>Throwable</code> exception that interfered with the
+     *            JSP Tag's normal operation, making the JSP Tag exception
+     *            necessary
+     * @since 2.0
+     */
+    public JspTagException(Throwable rootCause) {
+        super(rootCause);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspWriter.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspWriter.java
new file mode 100644
index 0000000..4348c43
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/JspWriter.java
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.jsp;
+
+import java.io.IOException;
+
+/**
+ * <p>
+ * The actions and template data in a JSP page is written using the JspWriter
+ * object that is referenced by the implicit variable out which is initialized
+ * automatically using methods in the PageContext object.
+ *<p>
+ * This abstract class emulates some of the functionality found in the
+ * java.io.BufferedWriter and java.io.PrintWriter classes, however it differs in
+ * that it throws java.io.IOException from the print methods while PrintWriter
+ * does not.
+ * <p>
+ * <B>Buffering</B>
+ * <p>
+ * The initial JspWriter object is associated with the PrintWriter object of the
+ * ServletResponse in a way that depends on whether the page is or is not
+ * buffered. If the page is not buffered, output written to this JspWriter
+ * object will be written through to the PrintWriter directly, which will be
+ * created if necessary by invoking the getWriter() method on the response
+ * object. But if the page is buffered, the PrintWriter object will not be
+ * created until the buffer is flushed and operations like setContentType() are
+ * legal. Since this flexibility simplifies programming substantially, buffering
+ * is the default for JSP pages.
+ * <p>
+ * Buffering raises the issue of what to do when the buffer is exceeded. Two
+ * approaches can be taken:
+ * <ul>
+ * <li>Exceeding the buffer is not a fatal error; when the buffer is exceeded,
+ * just flush the output.
+ * <li>Exceeding the buffer is a fatal error; when the buffer is exceeded, raise
+ * an exception.
+ * </ul>
+ * <p>
+ * Both approaches are valid, and thus both are supported in the JSP technology.
+ * The behavior of a page is controlled by the autoFlush attribute, which
+ * defaults to true. In general, JSP pages that need to be sure that correct and
+ * complete data has been sent to their client may want to set autoFlush to
+ * false, with a typical case being that where the client is an application
+ * itself. On the other hand, JSP pages that send data that is meaningful even
+ * when partially constructed may want to set autoFlush to true; such as when
+ * the data is sent for immediate display through a browser. Each application
+ * will need to consider their specific needs.
+ * <p>
+ * An alternative considered was to make the buffer size unbounded; but, this
+ * had the disadvantage that runaway computations would consume an unbounded
+ * amount of resources.
+ * <p>
+ * The "out" implicit variable of a JSP implementation class is of this type. If
+ * the page directive selects autoflush="true" then all the I/O operations on
+ * this class shall automatically flush the contents of the buffer if an
+ * overflow condition would result if the current operation were performed
+ * without a flush. If autoflush="false" then all the I/O operations on this
+ * class shall throw an IOException if performing the current operation would
+ * result in a buffer overflow condition.
+ * 
+ * @see java.io.Writer
+ * @see java.io.BufferedWriter
+ * @see java.io.PrintWriter
+ */
+public abstract class JspWriter extends java.io.Writer {
+
+    /**
+     * Constant indicating that the Writer is not buffering output.
+     */
+    public static final int NO_BUFFER = 0;
+
+    /**
+     * Constant indicating that the Writer is buffered and is using the
+     * implementation default buffer size.
+     */
+    public static final int DEFAULT_BUFFER = -1;
+
+    /**
+     * Constant indicating that the Writer is buffered and is unbounded; this is
+     * used in BodyContent.
+     */
+    public static final int UNBOUNDED_BUFFER = -2;
+
+    /**
+     * Protected constructor.
+     * 
+     * @param bufferSize
+     *            the size of the buffer to be used by the JspWriter
+     * @param autoFlush
+     *            whether the JspWriter should be autoflushing
+     */
+    protected JspWriter(int bufferSize, boolean autoFlush) {
+        this.bufferSize = bufferSize;
+        this.autoFlush = autoFlush;
+    }
+
+    /**
+     * Write a line separator. The line separator string is defined by the
+     * system property <tt>line.separator</tt>, and is not necessarily a single
+     * newline ('\n') character.
+     * 
+     * @exception IOException
+     *                If an I/O error occurs
+     */
+    public abstract void newLine() throws IOException;
+
+    /**
+     * Print a boolean value. The string produced by <code>{@link
+     * java.lang.String#valueOf(boolean)}</code>
+     * is written to the JspWriter's buffer or, if no buffer is used, directly
+     * to the underlying writer.
+     * 
+     * @param b
+     *            The <code>boolean</code> to be printed
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(boolean b) throws IOException;
+
+    /**
+     * Print a character. The character is written to the JspWriter's buffer or,
+     * if no buffer is used, directly to the underlying writer.
+     * 
+     * @param c
+     *            The <code>char</code> to be printed
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(char c) throws IOException;
+
+    /**
+     * Print an integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(int)}</code>
+     * is written to the JspWriter's buffer or, if no buffer is used, directly
+     * to the underlying writer.
+     * 
+     * @param i
+     *            The <code>int</code> to be printed
+     * @see java.lang.Integer#toString(int)
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(int i) throws IOException;
+
+    /**
+     * Print a long integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(long)}</code>
+     * is written to the JspWriter's buffer or, if no buffer is used, directly
+     * to the underlying writer.
+     * 
+     * @param l
+     *            The <code>long</code> to be printed
+     * @see java.lang.Long#toString(long)
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(long l) throws IOException;
+
+    /**
+     * Print a floating-point number. The string produced by <code>{@link
+     * java.lang.String#valueOf(float)}</code>
+     * is written to the JspWriter's buffer or, if no buffer is used, directly
+     * to the underlying writer.
+     * 
+     * @param f
+     *            The <code>float</code> to be printed
+     * @see java.lang.Float#toString(float)
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(float f) throws IOException;
+
+    /**
+     * Print a double-precision floating-point number. The string produced by
+     * <code>{@link java.lang.String#valueOf(double)}</code> is written to the
+     * JspWriter's buffer or, if no buffer is used, directly to the underlying
+     * writer.
+     * 
+     * @param d
+     *            The <code>double</code> to be printed
+     * @see java.lang.Double#toString(double)
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(double d) throws IOException;
+
+    /**
+     * Print an array of characters. The characters are written to the
+     * JspWriter's buffer or, if no buffer is used, directly to the underlying
+     * writer.
+     * 
+     * @param s
+     *            The array of chars to be printed
+     * @throws NullPointerException
+     *             If <code>s</code> is <code>null</code>
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(char s[]) throws IOException;
+
+    /**
+     * Print a string. If the argument is <code>null</code> then the string
+     * <code>"null"</code> is printed. Otherwise, the string's characters are
+     * written to the JspWriter's buffer or, if no buffer is used, directly to
+     * the underlying writer.
+     * 
+     * @param s
+     *            The <code>String</code> to be printed
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(String s) throws IOException;
+
+    /**
+     * Print an object. The string produced by the <code>{@link
+     * java.lang.String#valueOf(Object)}</code>
+     * method is written to the JspWriter's buffer or, if no buffer is used,
+     * directly to the underlying writer.
+     * 
+     * @param obj
+     *            The <code>Object</code> to be printed
+     * @see java.lang.Object#toString()
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void print(Object obj) throws IOException;
+
+    /**
+     * 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>).
+     * 
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println() throws IOException;
+
+    /**
+     * 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 boolean to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(boolean x) throws IOException;
+
+    /**
+     * 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 char to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(char x) throws IOException;
+
+    /**
+     * 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 int to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(int x) throws IOException;
+
+    /**
+     * 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 long to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(long x) throws IOException;
+
+    /**
+     * 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 float to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(float x) throws IOException;
+
+    /**
+     * 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 double to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(double x) throws IOException;
+
+    /**
+     * Print an array of characters and then terminate the line. This method
+     * behaves as though it invokes <code>print(char[])</code> and then
+     * <code>println()</code>.
+     * 
+     * @param x
+     *            the char[] to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(char x[]) throws IOException;
+
+    /**
+     * 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 String to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(String x) throws IOException;
+
+    /**
+     * 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 Object to write
+     * @throws java.io.IOException
+     *             If an error occurred while writing
+     */
+    public abstract void println(Object x) throws IOException;
+
+    /**
+     * Clear the contents of the buffer. If the buffer has been already been
+     * flushed then the clear operation shall throw an IOException to signal the
+     * fact that some data has already been irrevocably written to the client
+     * response stream.
+     * 
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public abstract void clear() throws IOException;
+
+    /**
+     * Clears the current contents of the buffer. Unlike clear(), this method
+     * will not throw an IOException if the buffer has already been flushed. It
+     * merely clears the current content of the buffer and returns.
+     * 
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public abstract void clearBuffer() throws IOException;
+
+    /**
+     * Flush the stream. If the stream has saved any characters from the various
+     * write() methods in a buffer, write them immediately to their intended
+     * destination. Then, if that destination is another character or byte
+     * stream, flush it. Thus one flush() invocation will flush all the buffers
+     * in a chain of Writers and OutputStreams.
+     * <p>
+     * The method may be invoked indirectly if the buffer size is exceeded.
+     * <p>
+     * Once a stream has been closed, further write() or flush() invocations
+     * will cause an IOException to be thrown.
+     * 
+     * @exception IOException
+     *                If an I/O error occurs
+     */
+    @Override
+    public abstract void flush() throws IOException;
+
+    /**
+     * Close the stream, flushing it first.
+     * <p>
+     * This method needs not be invoked explicitly for the initial JspWriter as
+     * the code generated by the JSP container will automatically include a call
+     * to close().
+     * <p>
+     * Closing a previously-closed stream, unlike flush(), has no effect.
+     * 
+     * @exception IOException
+     *                If an I/O error occurs
+     */
+    @Override
+    public abstract void close() throws IOException;
+
+    /**
+     * This method returns the size of the buffer used by the JspWriter.
+     * 
+     * @return the size of the buffer in bytes, or 0 is unbuffered.
+     */
+    public int getBufferSize() {
+        return bufferSize;
+    }
+
+    /**
+     * This method returns the number of unused bytes in the buffer.
+     * 
+     * @return the number of bytes unused in the buffer
+     */
+    public abstract int getRemaining();
+
+    /**
+     * This method indicates whether the JspWriter is autoFlushing.
+     * 
+     * @return if this JspWriter is auto flushing or throwing IOExceptions on
+     *         buffer overflow conditions
+     */
+    public boolean isAutoFlush() {
+        return autoFlush;
+    }
+
+    /*
+     * fields
+     */
+
+    /**
+     * The size of the buffer used by the JspWriter.
+     */
+    protected int bufferSize;
+
+    /**
+     * Whether the JspWriter is autoflushing.
+     */
+    protected boolean autoFlush;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/PageContext.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/PageContext.java
new file mode 100644
index 0000000..b320a02
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/PageContext.java
@@ -0,0 +1,535 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ 
+package javax.servlet.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+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.http.HttpSession;
+import javax.servlet.jsp.tagext.BodyContent;
+
+/**
+ * <p>
+ * PageContext extends JspContext to provide useful context information for
+ * when JSP technology is used in a Servlet environment.
+ * <p>
+ * A PageContext instance provides access to all the namespaces associated
+ * with a JSP page, provides access to several page attributes, as well as
+ * a layer above the implementation details.  Implicit objects are added
+ * to the pageContext automatically.
+ *
+ * <p> The <code> PageContext </code> class is an abstract class, designed to be
+ * extended to provide implementation dependent implementations thereof, by
+ * conformant JSP engine runtime environments. A PageContext instance is 
+ * obtained by a JSP implementation class by calling the
+ * JspFactory.getPageContext() method, and is released by calling
+ * JspFactory.releasePageContext().
+ *
+ * <p> An example of how PageContext, JspFactory, and other classes can be
+ * used  within a JSP Page Implementation object is given elsewhere.
+ *
+ * <p>
+ * The PageContext provides a number of facilities to the page/component 
+ * author and page implementor, including:
+ * <ul>
+ * <li>a single API to manage the various scoped namespaces
+ * <li>a number of convenience API's to access various public objects
+ * <li>a mechanism to obtain the JspWriter for output
+ * <li>a mechanism to manage session usage by the page
+ * <li>a mechanism to expose page directive attributes to the scripting 
+ *     environment
+ * <li>mechanisms to forward or include the current request to other active 
+ *     components in the application
+ * <li>a mechanism to handle errorpage exception processing
+ * </ul>
+ *
+ * <p><B>Methods Intended for Container Generated Code</B>
+ * <p>Some methods are intended to be used by the code generated by the
+ * container, not by code written by JSP page authors, or JSP tag library 
+ * authors.
+ * <p>The methods supporting <B>lifecycle</B> are <code>initialize()</code>
+ * and <code>release()</code>
+ *
+ * <p>
+ * The following methods enable the <B>management of nested</B> JspWriter 
+ * streams to implement Tag Extensions: <code>pushBody()</code>
+ *
+ * <p><B>Methods Intended for JSP authors</B>
+ * <p>
+ * The following methods provide <B>convenient access</B> to implicit objects:
+ * <code>getException()</code>,  <code>getPage()</code>
+ * <code>getRequest()</code>,  <code>getResponse()</code>,
+ * <code>getSession()</code>,  <code>getServletConfig()</code>
+ * and <code>getServletContext()</code>.
+ *
+ * <p>
+ * The following methods provide support for <B>forwarding, inclusion
+ * and error handling</B>:
+ * <code>forward()</code>,  <code>include()</code>,
+ * and  <code>handlePageException()</code>.
+ */
+
+public abstract class PageContext 
+    extends JspContext
+{
+    
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public PageContext() {
+        // NOOP by default
+    }
+    
+    /**
+     * Page scope: (this is the default) the named reference remains available
+     * in this PageContext until the return from the current Servlet.service()
+     * invocation.
+     */
+
+    public static final int PAGE_SCOPE = 1;
+
+    /**
+     * Request scope: the named reference remains available from the 
+     * ServletRequest associated with the Servlet until the current request 
+     * is completed.
+     */
+
+    public static final int REQUEST_SCOPE = 2;
+
+    /**
+     * Session scope (only valid if this page participates in a session):
+     * the named reference remains available from the HttpSession (if any)
+     * associated with the Servlet until the HttpSession is invalidated.
+     */
+
+    public static final int SESSION_SCOPE = 3;
+
+    /**
+     * Application scope: named reference remains available in the 
+     * ServletContext until it is reclaimed.
+     */
+
+    public static final int APPLICATION_SCOPE = 4;
+
+    /**
+     * Name used to store the Servlet in this PageContext's nametables.
+     */
+
+    public static final String PAGE = "javax.servlet.jsp.jspPage";
+
+    /**
+     * Name used to store this PageContext in it's own name table.
+     */
+
+    public static final String PAGECONTEXT = "javax.servlet.jsp.jspPageContext";
+
+    /**
+     * Name used to store ServletRequest in PageContext name table.
+     */
+
+    public static final String REQUEST = "javax.servlet.jsp.jspRequest";
+
+    /**
+     * Name used to store ServletResponse in PageContext name table.
+     */
+
+    public static final String RESPONSE = "javax.servlet.jsp.jspResponse";
+
+    /**
+     * Name used to store ServletConfig in PageContext name table.
+     */
+
+    public static final String CONFIG = "javax.servlet.jsp.jspConfig";
+
+    /**
+     * Name used to store HttpSession in PageContext name table.
+     */
+
+    public static final String SESSION = "javax.servlet.jsp.jspSession";
+    /**
+     * Name used to store current JspWriter in PageContext name table.
+     */
+
+    public static final String OUT = "javax.servlet.jsp.jspOut";
+
+    /**
+     * Name used to store ServletContext in PageContext name table.
+     */
+
+    public static final String APPLICATION = "javax.servlet.jsp.jspApplication";
+
+    /**
+     * Name used to store uncaught exception in ServletRequest attribute 
+     * list and PageContext name table.
+     */
+
+    public static final String EXCEPTION = "javax.servlet.jsp.jspException";
+
+    /**
+     * <p>
+     * The initialize method is called to initialize an uninitialized PageContext
+     * so that it may be used by a JSP Implementation class to service an
+     * incoming request and response within it's _jspService() method.
+     *
+     * <p>
+     * This method is typically called from JspFactory.getPageContext() in
+     * order to initialize state.
+     *
+     * <p>
+     * This method is required to create an initial JspWriter, and associate
+     * the "out" name in page scope with this newly created object.
+     *
+     * <p>
+     * This method should not be used by page  or tag library authors.
+     *
+     * @param servlet The Servlet that is associated with this PageContext
+     * @param request The currently pending request for this Servlet
+     * @param response The currently pending response for this Servlet
+     * @param errorPageURL The value of the errorpage attribute from the page 
+     *     directive or null
+     * @param needsSession The value of the session attribute from the 
+     *     page directive
+     * @param bufferSize The value of the buffer attribute from the page 
+     *     directive
+     * @param autoFlush The value of the autoflush attribute from the page 
+     *     directive
+     *
+     * @throws IOException during creation of JspWriter
+     * @throws IllegalStateException if out not correctly initialized
+     * @throws IllegalArgumentException If one of the given parameters
+     *     is invalid
+     */
+ 
+    public abstract void initialize(Servlet servlet, ServletRequest request, 
+        ServletResponse response, String errorPageURL, boolean needsSession, 
+        int bufferSize, boolean autoFlush)  
+        throws IOException, IllegalStateException, IllegalArgumentException;
+
+    /**
+     * <p>
+     * This method shall "reset" the internal state of a PageContext, releasing
+     * all internal references, and preparing the PageContext for potential
+     * reuse by a later invocation of initialize(). This method is typically
+     * called from JspFactory.releasePageContext().
+     *
+     * <p>
+     * Subclasses shall envelope this method.
+     *
+     * <p>
+     * This method should not be used by page  or tag library authors.
+     *
+     */
+
+    public abstract void release();
+
+    /**
+     * The current value of the session object (an HttpSession).
+     *
+     * @return the HttpSession for this PageContext or null
+     */
+
+    public abstract HttpSession getSession();
+
+    /**
+     * The current value of the page object (In a Servlet environment, 
+     * this is an instance of javax.servlet.Servlet).
+     *
+     * @return the Page implementation class instance associated 
+     *     with this PageContext
+     */
+
+    public abstract Object getPage();
+
+
+    /**
+     * The current value of the request object (a ServletRequest).
+     *
+     * @return The ServletRequest for this PageContext
+     */
+
+    public abstract ServletRequest getRequest();
+
+    /**
+     * The current value of the response object (a ServletResponse).
+     *
+     * @return the ServletResponse for this PageContext
+     */
+
+    public abstract ServletResponse getResponse();
+
+    /**
+     * The current value of the exception object (an Exception).
+     *
+     * @return any exception passed to this as an errorpage
+     */
+
+    public abstract Exception getException();
+
+    /**
+     * The ServletConfig instance.
+     *
+     * @return the ServletConfig for this PageContext
+     */
+
+    public abstract ServletConfig getServletConfig();
+
+    /**
+     * The ServletContext instance.
+     * 
+     * @return the ServletContext for this PageContext
+     */
+
+    public abstract ServletContext getServletContext();
+
+    /**
+     * <p>
+     * This method is used to re-direct, or "forward" the current 
+     * ServletRequest and ServletResponse to another active component in 
+     * the application.
+     * </p>
+     * <p>
+     * If the <I> relativeUrlPath </I> begins with a "/" then the URL specified
+     * is calculated relative to the DOCROOT of the <code> ServletContext </code>
+     * for this JSP. If the path does not begin with a "/" then the URL 
+     * specified is calculated relative to the URL of the request that was
+     * mapped to the calling JSP.
+     * </p>
+     * <p>
+     * It is only valid to call this method from a <code> Thread </code>
+     * executing within a <code> _jspService(...) </code> method of a JSP.
+     * </p>
+     * <p>
+     * Once this method has been called successfully, it is illegal for the
+     * calling <code> Thread </code> to attempt to modify the <code>
+     * ServletResponse </code> object.  Any such attempt to do so, shall result
+     * in undefined behavior. Typically, callers immediately return from 
+     * <code> _jspService(...) </code> after calling this method.
+     * </p>
+     *
+     * @param relativeUrlPath specifies the relative URL path to the target 
+     *     resource as described above
+     *
+     * @throws IllegalStateException if <code> ServletResponse </code> is not 
+     *     in a state where a forward can be performed
+     * @throws ServletException if the page that was forwarded to throws
+     *     a ServletException
+     * @throws IOException if an I/O error occurred while forwarding
+     */
+
+    public abstract void forward(String relativeUrlPath) 
+        throws ServletException, IOException;
+
+    /**
+     * <p>
+     * Causes the resource specified to be processed as part of the current
+     * ServletRequest and ServletResponse being processed by the calling Thread.
+     * The output of the target resources processing of the request is written
+     * directly to the ServletResponse output stream.
+     * </p>
+     * <p>
+     * The current JspWriter "out" for this JSP is flushed as a side-effect
+     * of this call, prior to processing the include.
+     * </p>
+     * <p>
+     * If the <I> relativeUrlPath </I> begins with a "/" then the URL specified
+     * is calculated relative to the DOCROOT of the <code>ServletContext</code>
+     * for this JSP. If the path does not begin with a "/" then the URL 
+     * specified is calculated relative to the URL of the request that was
+     * mapped to the calling JSP.
+     * </p>
+     * <p>
+     * It is only valid to call this method from a <code> Thread </code>
+     * executing within a <code> _jspService(...) </code> method of a JSP.
+     * </p>
+     *
+     * @param relativeUrlPath specifies the relative URL path to the target 
+     *     resource to be included
+     *
+     * @throws ServletException if the page that was forwarded to throws
+     *     a ServletException
+     * @throws IOException if an I/O error occurred while forwarding
+     */
+    public abstract void include(String relativeUrlPath) 
+        throws ServletException, IOException;
+
+    /**
+     * <p>
+     * Causes the resource specified to be processed as part of the current
+     * ServletRequest and ServletResponse being processed by the calling Thread.
+     * The output of the target resources processing of the request is written
+     * directly to the current JspWriter returned by a call to getOut().
+     * </p>
+     * <p>
+     * If flush is true, The current JspWriter "out" for this JSP 
+     * is flushed as a side-effect of this call, prior to processing 
+     * the include.  Otherwise, the JspWriter "out" is not flushed.
+     * </p>
+     * <p>
+     * If the <i>relativeUrlPath</i> begins with a "/" then the URL specified
+     * is calculated relative to the DOCROOT of the <code>ServletContext</code>
+     * for this JSP. If the path does not begin with a "/" then the URL 
+     * specified is calculated relative to the URL of the request that was
+     * mapped to the calling JSP.
+     * </p>
+     * <p>
+     * It is only valid to call this method from a <code> Thread </code>
+     * executing within a <code> _jspService(...) </code> method of a JSP.
+     * </p>
+     *
+     * @param relativeUrlPath specifies the relative URL path to the 
+     *     target resource to be included
+     * @param flush True if the JspWriter is to be flushed before the include,
+     *     or false if not.
+     *
+     * @throws ServletException if the page that was forwarded to throws
+     *     a ServletException
+     * @throws IOException if an I/O error occurred while forwarding
+     * @since 2.0
+     */
+    public abstract void include(String relativeUrlPath, boolean flush) 
+        throws ServletException, IOException;
+
+    /**
+     * <p>
+     * This method is intended to process an unhandled 'page' level
+     * exception by forwarding the exception to the specified
+     * error page for this JSP.  If forwarding is not possible (for
+     * example because the response has already been committed), an
+     * implementation dependent mechanism should be used to invoke
+     * the error page (e.g. "including" the error page instead).
+     *
+     * <p>
+     * If no error page is defined in the page, the exception should
+     * be rethrown so that the standard servlet error handling
+     * takes over.
+     *
+     * <p>
+     * A JSP implementation class shall typically clean up any local state
+     * prior to invoking this and will return immediately thereafter. It is
+     * illegal to generate any output to the client, or to modify any 
+     * ServletResponse state after invoking this call.
+     *
+     * <p>
+     * This method is kept for backwards compatibility reasons.  Newly
+     * generated code should use PageContext.handlePageException(Throwable).
+     *
+     * @param e the exception to be handled
+     *
+     * @throws ServletException if an error occurs while invoking the error page
+     * @throws IOException if an I/O error occurred while invoking the error
+     *     page
+     * @throws NullPointerException if the exception is null
+     *
+     * @see #handlePageException(Throwable)
+     */
+
+    public abstract void handlePageException(Exception e) 
+        throws ServletException, IOException;
+
+    /**
+     * <p>
+     * This method is intended to process an unhandled 'page' level
+     * exception by forwarding the exception to the specified
+     * error page for this JSP.  If forwarding is not possible (for
+     * example because the response has already been committed), an
+     * implementation dependent mechanism should be used to invoke
+     * the error page (e.g. "including" the error page instead).
+     *
+     * <p>
+     * If no error page is defined in the page, the exception should
+     * be rethrown so that the standard servlet error handling
+     * takes over.
+     *
+     * <p>
+     * This method is intended to process an unhandled "page" level exception
+     * by redirecting the exception to either the specified error page for this
+     * JSP, or if none was specified, to perform some implementation dependent
+     * action.
+     *
+     * <p>
+     * A JSP implementation class shall typically clean up any local state
+     * prior to invoking this and will return immediately thereafter. It is
+     * illegal to generate any output to the client, or to modify any 
+     * ServletResponse state after invoking this call.
+     *
+     * @param t the throwable to be handled
+     *
+     * @throws ServletException if an error occurs while invoking the error page
+     * @throws IOException if an I/O error occurred while invoking the error
+     *     page
+     * @throws NullPointerException if the exception is null
+     *
+     * @see #handlePageException(Exception)
+     */
+
+    public abstract void handlePageException(Throwable t) 
+        throws ServletException, IOException;
+
+    /**
+     * Return a new BodyContent object, save the current "out" JspWriter,
+     * and update the value of the "out" attribute in the page scope
+     * attribute namespace of the PageContext.
+     *
+     * @return the new BodyContent
+     */
+
+    public BodyContent pushBody() {
+        return null; // XXX to implement
+    }
+         
+
+    /**
+     * Provides convenient access to error information.
+     *
+     * @return an ErrorData instance containing information about the 
+     * error, as obtained from the request attributes, as per the 
+     * Servlet specification.  If this is not an error page (that is,
+     * if the isErrorPage attribute of the page directive is not set
+     * to "true"), the information is meaningless.
+     *
+     * @since 2.0
+     */
+    public ErrorData getErrorData() {
+        int status = 0;
+        
+        Integer status_code = (Integer)getRequest().getAttribute( 
+                RequestDispatcher.ERROR_STATUS_CODE);
+        // Avoid NPE if attribute is not set
+        if (status_code != null) {
+            status = status_code.intValue();
+        }
+
+        return new ErrorData( 
+            (Throwable)getRequest().getAttribute(
+                    RequestDispatcher.ERROR_EXCEPTION),
+            status,
+            (String)getRequest().getAttribute(
+                    RequestDispatcher.ERROR_REQUEST_URI),
+            (String)getRequest().getAttribute(
+                    RequestDispatcher.ERROR_SERVLET_NAME)
+            );
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/SkipPageException.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/SkipPageException.java
new file mode 100644
index 0000000..ba0e15a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/SkipPageException.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.jsp;
+
+/**
+ * Exception to indicate the calling page must cease evaluation. Thrown by a
+ * simple tag handler to indicate that the remainder of the page must not be
+ * evaluated. The result is propagated back to the page in the case where one
+ * tag invokes another (as can be the case with tag files). The effect is
+ * similar to that of a Classic Tag Handler returning Tag.SKIP_PAGE from
+ * doEndTag(). Jsp Fragments may also throw this exception. This exception
+ * should not be thrown manually in a JSP page or tag file - the behavior is
+ * undefined. The exception is intended to be thrown inside SimpleTag handlers
+ * and in JSP fragments.
+ * 
+ * @see javax.servlet.jsp.tagext.SimpleTag#doTag
+ * @see javax.servlet.jsp.tagext.JspFragment#invoke
+ * @see javax.servlet.jsp.tagext.Tag#doEndTag
+ * @since 2.0
+ */
+public class SkipPageException extends JspException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates a SkipPageException with no message.
+     */
+    public SkipPageException() {
+        super();
+    }
+
+    /**
+     * Creates a SkipPageException with the provided message.
+     * 
+     * @param message
+     *            the detail message
+     */
+    public SkipPageException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a SkipPageException with the provided message and root cause.
+     * 
+     * @param message
+     *            the detail message
+     * @param rootCause
+     *            the originating cause of this exception
+     */
+    public SkipPageException(String message, Throwable rootCause) {
+        super(message, rootCause);
+    }
+
+    /**
+     * Creates a SkipPageException with the provided root cause.
+     * 
+     * @param rootCause
+     *            the originating cause of this exception
+     */
+    public SkipPageException(Throwable rootCause) {
+        super(rootCause);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ELException.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ELException.java
new file mode 100644
index 0000000..f6bdd5c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ELException.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.jsp.el;
+
+/**
+ * Represents any of the exception conditions that arise during the operation
+ * evaluation of the evaluator.
+ * 
+ * @since 2.0
+ * @deprecated
+ */
+@SuppressWarnings("dep-ann") // TCK signature test fails with annotation
+public class ELException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates an ELException with no detail message.
+     **/
+    public ELException() {
+        super();
+    }
+
+    /**
+     * Creates an ELException with the provided detail message.
+     * 
+     * @param pMessage
+     *            the detail message
+     **/
+    public ELException(String pMessage) {
+        super(pMessage);
+    }
+
+    /**
+     * Creates an ELException with the given root cause.
+     * 
+     * @param pRootCause
+     *            the originating cause of this exception
+     **/
+    public ELException(Throwable pRootCause) {
+        super(pRootCause);
+    }
+
+    // -------------------------------------
+    /**
+     * Creates an ELException with the given detail message and root cause.
+     * 
+     * @param pMessage
+     *            the detail message
+     * @param pRootCause
+     *            the originating cause of this exception
+     **/
+    public ELException(String pMessage, Throwable pRootCause) {
+        super(pMessage, pRootCause);
+    }
+
+    // -------------------------------------
+    /**
+     * Returns the root cause.
+     * 
+     * @return the root cause of this exception
+     */
+    public Throwable getRootCause() {
+        return getCause();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ELParseException.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ELParseException.java
new file mode 100644
index 0000000..35e4513
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ELParseException.java
@@ -0,0 +1,53 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package javax.servlet.jsp.el;
+
+
+/**
+ * Represents a parsing error encountered while parsing an EL expression.
+ *
+ * @since 2.0
+ * @deprecated
+ */
+@SuppressWarnings("dep-ann") // TCK signature test fails with annotation
+public class ELParseException extends ELException {
+
+ private static final long serialVersionUID = 1L;
+
+//-------------------------------------
+  /**
+   * Creates an ELParseException with no detail message.
+   */
+  public ELParseException ()
+  {
+    super ();
+  }
+
+  //-------------------------------------
+  /**
+   * Creates an ELParseException with the provided detail message.
+   *
+   * @param pMessage the detail message
+   **/
+  public ELParseException (String pMessage)
+  {
+    super (pMessage);
+  }
+
+  //-------------------------------------
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/Expression.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/Expression.java
new file mode 100644
index 0000000..b1ceb35
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/Expression.java
@@ -0,0 +1,54 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+
+package javax.servlet.jsp.el;
+
+
+/**
+ * <p>The abstract class for a prepared expression.</p>
+ *
+ * <p>An instance of an Expression can be obtained via from an 
+ * ExpressionEvaluator instance.</p>
+ *
+ * <p>An Expression may or not have done a syntactic parse of the expression.
+ * A client invoking the evaluate() method should be ready for the case 
+ * where ELParseException exceptions are raised. </p>
+ *
+ * @since 2.0
+ * @deprecated
+ */
+@SuppressWarnings("dep-ann") // TCK signature test fails with annotation
+public abstract class Expression {
+
+    /** 
+     * Evaluates an expression that was previously prepared.  In some 
+     * implementations preparing an expression involves full syntactic 
+     * validation, but others may not do so.  Evaluating the expression may 
+     * raise an ELParseException as well as other ELExceptions due to 
+     * run-time evaluation.
+     *
+     * @param vResolver A VariableResolver instance that can be used at 
+     *   runtime to resolve the name of implicit objects into Objects.
+     * @return The result of the expression evaluation.
+     *
+     * @exception ELException Thrown if the expression evaluation failed.
+     */ 
+    public abstract Object evaluate( VariableResolver vResolver )
+        throws ELException;
+}
+
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ExpressionEvaluator.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ExpressionEvaluator.java
new file mode 100644
index 0000000..785c6f8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ExpressionEvaluator.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.servlet.jsp.el;
+
+/**
+ * <p>
+ * The abstract base class for an expression-language evaluator. Classes that
+ * implement an expression language expose their functionality via this abstract
+ * class.
+ * </p>
+ * <p>
+ * An instance of the ExpressionEvaluator can be obtained via the JspContext /
+ * PageContext
+ * </p>
+ * <p>
+ * The parseExpression() and evaluate() methods must be thread-safe. That is,
+ * multiple threads may call these methods on the same ExpressionEvaluator
+ * object simultaneously. Implementations should synchronize access if they
+ * depend on transient state. Implementations should not, however, assume that
+ * only one object of each ExpressionEvaluator type will be instantiated; global
+ * caching should therefore be static.
+ * </p>
+ * <p>
+ * Only a single EL expression, starting with '${' and ending with '}', can be
+ * parsed or evaluated at a time. EL expressions cannot be mixed with static
+ * text. For example, attempting to parse or evaluate "
+ * <code>abc${1+1}def${1+1}ghi</code>" or even "<code>${1+1}${1+1}</code>" will
+ * cause an <code>ELException</code> to be thrown.
+ * </p>
+ * <p>
+ * The following are examples of syntactically legal EL expressions:
+ * <ul>
+ * <li><code>${person.lastName}</code></li>
+ * <li><code>${8 * 8}</code></li>
+ * <li><code>${my:reverse('hello')}</code></li>
+ * </ul>
+ * </p>
+ * 
+ * @since 2.0
+ * @deprecated
+ */
+@SuppressWarnings("dep-ann")
+// TCK signature test fails with annotation
+public abstract class ExpressionEvaluator {
+
+    /**
+     * Prepare an expression for later evaluation. This method should perform
+     * syntactic validation of the expression; if in doing so it detects errors,
+     * it should raise an ELParseException.
+     * 
+     * @param expression
+     *            The expression to be evaluated.
+     * @param expectedType
+     *            The expected type of the result of the evaluation
+     * @param fMapper
+     *            A FunctionMapper to resolve functions found in the expression.
+     *            It can be null, in which case no functions are supported for
+     *            this invocation. The ExpressionEvaluator must not hold on to
+     *            the FunctionMapper reference after returning from
+     *            <code>parseExpression()</code>. The <code>Expression</code>
+     *            object returned must invoke the same functions regardless of
+     *            whether the mappings in the provided
+     *            <code>FunctionMapper</code> instance change between calling
+     *            <code>ExpressionEvaluator.parseExpression()</code> and
+     *            <code>Expression.evaluate()</code>.
+     * @return The Expression object encapsulating the arguments.
+     * @exception ELException
+     *                Thrown if parsing errors were found.
+     */
+    public abstract Expression parseExpression(String expression,
+            @SuppressWarnings("rawtypes")// TCK signature fails with generics
+            Class expectedType, FunctionMapper fMapper) throws ELException;
+
+    /**
+     * Evaluates an expression. This method may perform some syntactic
+     * validation and, if so, it should raise an ELParseException error if it
+     * encounters syntactic errors. EL evaluation errors should cause an
+     * ELException to be raised.
+     * 
+     * @param expression
+     *            The expression to be evaluated.
+     * @param expectedType
+     *            The expected type of the result of the evaluation
+     * @param vResolver
+     *            A VariableResolver instance that can be used at runtime to
+     *            resolve the name of implicit objects into Objects.
+     * @param fMapper
+     *            A FunctionMapper to resolve functions found in the expression.
+     *            It can be null, in which case no functions are supported for
+     *            this invocation.
+     * @return The result of the expression evaluation.
+     * @exception ELException
+     *                Thrown if the expression evaluation failed.
+     */
+    public abstract Object evaluate(
+            String expression,
+            @SuppressWarnings("rawtypes")// TCK signature fails with generics
+            Class expectedType, VariableResolver vResolver,
+            FunctionMapper fMapper) throws ELException;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/FunctionMapper.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/FunctionMapper.java
new file mode 100644
index 0000000..5b8b83d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/FunctionMapper.java
@@ -0,0 +1,41 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package javax.servlet.jsp.el;
+
+/**
+ * <p>The interface to a map between EL function names and methods.</p>
+ *
+ * <p>Classes implementing this interface may, for instance, consult tag library
+ * information to resolve the map. </p>
+ *
+ * @since 2.0
+ * @deprecated
+ */
+@SuppressWarnings("dep-ann") // TCK signature test fails with annotation
+public interface FunctionMapper {
+  /**
+   * Resolves the specified local name and prefix into a Java.lang.Method.
+   * Returns null if the prefix and local name are not found.
+   * 
+   * @param prefix the prefix of the function, or "" if no prefix.
+   * @param localName the short name of the function
+   * @return the result of the method mapping.  Null means no entry found.
+   **/
+  public java.lang.reflect.Method resolveFunction(String prefix, 
+      String localName);
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ImplicitObjectELResolver.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ImplicitObjectELResolver.java
new file mode 100644
index 0000000..588039d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ImplicitObjectELResolver.java
@@ -0,0 +1,630 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package javax.servlet.jsp.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.PageContext;
+
+/**
+ *
+ * @since 2.1
+ */
+public class ImplicitObjectELResolver extends ELResolver {
+
+    private static final String[] SCOPE_NAMES = new String[] {
+            "applicationScope", "cookie", "header", "headerValues",
+            "initParam", "pageContext", "pageScope", "param", "paramValues",
+            "requestScope", "sessionScope" };
+
+    private static final int APPLICATIONSCOPE = 0;
+
+    private static final int COOKIE = 1;
+
+    private static final int HEADER = 2;
+
+    private static final int HEADERVALUES = 3;
+
+    private static final int INITPARAM = 4;
+
+    private static final int PAGECONTEXT = 5;
+
+    private static final int PAGESCOPE = 6;
+
+    private static final int PARAM = 7;
+
+    private static final int PARAM_VALUES = 8;
+
+    private static final int REQUEST_SCOPE = 9;
+
+    private static final int SESSION_SCOPE = 10;
+
+    public ImplicitObjectELResolver() {
+        super();
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null && property != null) {
+            int idx = Arrays.binarySearch(SCOPE_NAMES, property.toString());
+
+            if (idx >= 0) {
+                PageContext page = (PageContext) context
+                        .getContext(JspContext.class);
+                context.setPropertyResolved(true);
+                switch (idx) {
+                case APPLICATIONSCOPE:
+                    return ScopeManager.get(page).getApplicationScope();
+                case COOKIE:
+                    return ScopeManager.get(page).getCookie();
+                case HEADER:
+                    return ScopeManager.get(page).getHeader();
+                case HEADERVALUES:
+                    return ScopeManager.get(page).getHeaderValues();
+                case INITPARAM:
+                    return ScopeManager.get(page).getInitParam();
+                case PAGECONTEXT:
+                    return ScopeManager.get(page).getPageContext();
+                case PAGESCOPE:
+                    return ScopeManager.get(page).getPageScope();
+                case PARAM:
+                    return ScopeManager.get(page).getParam();
+                case PARAM_VALUES:
+                    return ScopeManager.get(page).getParamValues();
+                case REQUEST_SCOPE:
+                    return ScopeManager.get(page).getRequestScope();
+                case SESSION_SCOPE:
+                    return ScopeManager.get(page).getSessionScope();
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings({ "unchecked", "rawtypes" }) // TCK signature test fails with generics
+    public Class getType(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null && property != null) {
+            int idx = Arrays.binarySearch(SCOPE_NAMES, property.toString());
+            if (idx >= 0) {
+                context.setPropertyResolved(true);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void setValue(ELContext context, Object base, Object property,
+            Object value) throws NullPointerException,
+            PropertyNotFoundException, PropertyNotWritableException,
+            ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null && property != null) {
+            int idx = Arrays.binarySearch(SCOPE_NAMES, property.toString());
+            if (idx >= 0) {
+                context.setPropertyResolved(true);
+                throw new PropertyNotWritableException();
+            }
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null && property != null) {
+            int idx = Arrays.binarySearch(SCOPE_NAMES, property.toString());
+            if (idx >= 0) {
+                context.setPropertyResolved(true);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+        List<FeatureDescriptor> feats = new ArrayList<FeatureDescriptor>(
+                SCOPE_NAMES.length);
+        FeatureDescriptor feat;
+        for (int i = 0; i < SCOPE_NAMES.length; i++) {
+            feat = new FeatureDescriptor();
+            feat.setDisplayName(SCOPE_NAMES[i]);
+            feat.setExpert(false);
+            feat.setHidden(false);
+            feat.setName(SCOPE_NAMES[i]);
+            feat.setPreferred(true);
+            feat.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
+            feat.setValue(TYPE, String.class);
+            feats.add(feat);
+        }
+        return feats.iterator();
+    }
+
+    @Override
+    public Class<String> getCommonPropertyType(ELContext context, Object base) {
+        if (base == null) {
+            return String.class;
+        }
+        return null;
+    }
+
+    private static class ScopeManager {
+        private static final String MNGR_KEY = ScopeManager.class.getName();
+
+        private final PageContext page;
+
+        private Map<String,Object> applicationScope;
+
+        private Map<String,Cookie> cookie;
+
+        private Map<String,String> header;
+
+        private Map<String,String[]> headerValues;
+
+        private Map<String,String> initParam;
+
+        private Map<String,Object> pageScope;
+
+        private Map<String,String> param;
+
+        private Map<String,String[]> paramValues;
+
+        private Map<String,Object> requestScope;
+
+        private Map<String,Object> sessionScope;
+
+        public ScopeManager(PageContext page) {
+            this.page = page;
+        }
+
+        public static ScopeManager get(PageContext page) {
+            ScopeManager mngr = (ScopeManager) page.getAttribute(MNGR_KEY);
+            if (mngr == null) {
+                mngr = new ScopeManager(page);
+                page.setAttribute(MNGR_KEY, mngr);
+            }
+            return mngr;
+        }
+
+        public Map<String,Object> getApplicationScope() {
+            if (this.applicationScope == null) {
+                this.applicationScope = new ScopeMap<Object>() {
+                    @Override
+                    protected void setAttribute(String name, Object value) {
+                        page.getServletContext().setAttribute(name, value);
+                    }
+
+                    @Override
+                    protected void removeAttribute(String name) {
+                        page.getServletContext().removeAttribute(name);
+                    }
+
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return page.getServletContext().getAttributeNames();
+                    }
+
+                    @Override
+                    protected Object getAttribute(String name) {
+                        return page.getServletContext().getAttribute(name);
+                    }
+                };
+            }
+            return this.applicationScope;
+        }
+
+        public Map<String,Cookie> getCookie() {
+            if (this.cookie == null) {
+                this.cookie = new ScopeMap<Cookie>() {
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        Cookie[] c = ((HttpServletRequest) page.getRequest())
+                                .getCookies();
+                        if (c != null) {
+                            Vector<String> v = new Vector<String>();
+                            for (int i = 0; i < c.length; i++) {
+                                v.add(c[i].getName());
+                            }
+                            return v.elements();
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    protected Cookie getAttribute(String name) {
+                        Cookie[] c = ((HttpServletRequest) page.getRequest())
+                                .getCookies();
+                        if (c != null) {
+                            for (int i = 0; i < c.length; i++) {
+                                if (name.equals(c[i].getName())) {
+                                    return c[i];
+                                }
+                            }
+                        }
+                        return null;
+                    }
+
+                };
+            }
+            return this.cookie;
+        }
+
+        public Map<String,String> getHeader() {
+            if (this.header == null) {
+                this.header = new ScopeMap<String>() {
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return ((HttpServletRequest) page.getRequest())
+                                .getHeaderNames();
+                    }
+
+                    @Override
+                    protected String getAttribute(String name) {
+                        return ((HttpServletRequest) page.getRequest())
+                                .getHeader(name);
+                    }
+                };
+            }
+            return this.header;
+        }
+
+        public Map<String,String[]> getHeaderValues() {
+            if (this.headerValues == null) {
+                this.headerValues = new ScopeMap<String[]>() {
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return ((HttpServletRequest) page.getRequest())
+                                .getHeaderNames();
+                    }
+
+                    @Override
+                    protected String[] getAttribute(String name) {
+                        Enumeration<String> e =
+                            ((HttpServletRequest) page.getRequest())
+                                    .getHeaders(name);
+                        if (e != null) {
+                            List<String> list = new ArrayList<String>();
+                            while (e.hasMoreElements()) {
+                                list.add(e.nextElement());
+                            }
+                            return list.toArray(new String[list.size()]);
+                        }
+                        return null;
+                    }
+
+                };
+            }
+            return this.headerValues;
+        }
+
+        public Map<String,String> getInitParam() {
+            if (this.initParam == null) {
+                this.initParam = new ScopeMap<String>() {
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return page.getServletContext().getInitParameterNames();
+                    }
+
+                    @Override
+                    protected String getAttribute(String name) {
+                        return page.getServletContext().getInitParameter(name);
+                    }
+                };
+            }
+            return this.initParam;
+        }
+
+        public PageContext getPageContext() {
+            return this.page;
+        }
+
+        public Map<String,Object> getPageScope() {
+            if (this.pageScope == null) {
+                this.pageScope = new ScopeMap<Object>() {
+                    @Override
+                    protected void setAttribute(String name, Object value) {
+                        page.setAttribute(name, value);
+                    }
+
+                    @Override
+                    protected void removeAttribute(String name) {
+                        page.removeAttribute(name);
+                    }
+
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return page.getAttributeNamesInScope(
+                                PageContext.PAGE_SCOPE);
+                    }
+
+                    @Override
+                    protected Object getAttribute(String name) {
+                        return page.getAttribute(name);
+                    }
+                };
+            }
+            return this.pageScope;
+        }
+
+        public Map<String,String> getParam() {
+            if (this.param == null) {
+                this.param = new ScopeMap<String>() {
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return page.getRequest().getParameterNames();
+                    }
+
+                    @Override
+                    protected String getAttribute(String name) {
+                        return page.getRequest().getParameter(name);
+                    }
+                };
+            }
+            return this.param;
+        }
+
+        public Map<String,String[]> getParamValues() {
+            if (this.paramValues == null) {
+                this.paramValues = new ScopeMap<String[]>() {
+                    @Override
+                    protected String[] getAttribute(String name) {
+                        return page.getRequest().getParameterValues(name);
+                    }
+
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return page.getRequest().getParameterNames();
+                    }
+                };
+            }
+            return this.paramValues;
+        }
+
+        public Map<String,Object> getRequestScope() {
+            if (this.requestScope == null) {
+                this.requestScope = new ScopeMap<Object>() {
+                    @Override
+                    protected void setAttribute(String name, Object value) {
+                        page.getRequest().setAttribute(name, value);
+                    }
+
+                    @Override
+                    protected void removeAttribute(String name) {
+                        page.getRequest().removeAttribute(name);
+                    }
+
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        return page.getRequest().getAttributeNames();
+                    }
+
+                    @Override
+                    protected Object getAttribute(String name) {
+                        return page.getRequest().getAttribute(name);
+                    }
+                };
+            }
+            return this.requestScope;
+        }
+
+        public Map<String,Object> getSessionScope() {
+            if (this.sessionScope == null) {
+                this.sessionScope = new ScopeMap<Object>() {
+                    @Override
+                    protected void setAttribute(String name, Object value) {
+                        ((HttpServletRequest) page.getRequest()).getSession()
+                                .setAttribute(name, value);
+                    }
+
+                    @Override
+                    protected void removeAttribute(String name) {
+                        HttpSession session = page.getSession();
+                        if (session != null) {
+                            session.removeAttribute(name);
+                        }
+                    }
+
+                    @Override
+                    protected Enumeration<String> getAttributeNames() {
+                        HttpSession session = page.getSession();
+                        if (session != null) {
+                            return session.getAttributeNames();
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    protected Object getAttribute(String name) {
+                        HttpSession session = page.getSession();
+                        if (session != null) {
+                            return session.getAttribute(name);
+                        }
+                        return null;
+                    }
+                };
+            }
+            return this.sessionScope;
+        }
+    }
+
+    private abstract static class ScopeMap<V> extends AbstractMap<String,V> {
+
+        protected abstract Enumeration<String> getAttributeNames();
+
+        protected abstract V getAttribute(String name);
+
+        @SuppressWarnings("unused")
+        protected void removeAttribute(String name) {
+            throw new UnsupportedOperationException();
+        }
+
+        @SuppressWarnings("unused")
+        protected void setAttribute(String name, Object value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public final Set<Map.Entry<String,V>> entrySet() {
+            Enumeration<String> e = getAttributeNames();
+            Set<Map.Entry<String, V>> set = new HashSet<Map.Entry<String, V>>();
+            if (e != null) {
+                while (e.hasMoreElements()) {
+                    set.add(new ScopeEntry(e.nextElement()));
+                }
+            }
+            return set;
+        }
+
+        @Override
+        public final int size() {
+            int size = 0;
+            Enumeration<String> e = getAttributeNames();
+            if (e != null) {
+                while (e.hasMoreElements()) {
+                    e.nextElement();
+                    size++;
+                }
+            }
+            return size;
+        }
+
+        @Override
+        public final boolean containsKey(Object key) {
+            if (key == null) {
+                return false;
+            }
+            Enumeration<String> e = getAttributeNames();
+            if (e != null) {
+                while (e.hasMoreElements()) {
+                    if (key.equals(e.nextElement())) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        private class ScopeEntry implements Map.Entry<String,V> {
+
+            private final String key;
+
+            public ScopeEntry(String key) {
+                this.key = key;
+            }
+
+            @Override
+            public String getKey() {
+                return this.key;
+            }
+
+            @Override
+            public V getValue() {
+                return getAttribute(this.key);
+            }
+
+            @Override
+            public V setValue(Object value) {
+                if (value == null) {
+                    removeAttribute(this.key);
+                } else {
+                    setAttribute(this.key, value);
+                }
+                return null;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                return (obj != null && this.hashCode() == obj.hashCode());
+            }
+
+            @Override
+            public int hashCode() {
+                return this.key.hashCode();
+            }
+
+        }
+
+        @Override
+        public final V get(Object key) {
+            if (key != null) {
+                return getAttribute((String) key);
+            }
+            return null;
+        }
+
+        @Override
+        public final V put(String key, V value) {
+            if (key == null) {
+                throw new NullPointerException();
+            }
+            if (value == null) {
+                this.removeAttribute(key);
+            } else {
+                this.setAttribute(key, value);
+            }
+            return null;
+        }
+
+        @Override
+        public final V remove(Object key) {
+            if (key == null) {
+                throw new NullPointerException();
+            }
+            this.removeAttribute((String) key);
+            return null;
+        }
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ScopedAttributeELResolver.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ScopedAttributeELResolver.java
new file mode 100644
index 0000000..de7fe39
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/ScopedAttributeELResolver.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.jsp.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.PageContext;
+
+public class ScopedAttributeELResolver extends ELResolver {
+
+    public ScopedAttributeELResolver() {
+        super();
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+            if (property != null) {
+                String key = property.toString();
+                PageContext page = (PageContext) context
+                        .getContext(JspContext.class);
+                return page.findAttribute(key);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public Class<Object> getType(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+            return Object.class;
+        }
+
+        return null;
+    }
+
+    @Override
+    public void setValue(ELContext context, Object base, Object property,
+            Object value) throws NullPointerException,
+            PropertyNotFoundException, PropertyNotWritableException,
+            ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+            if (property != null) {
+                String key = property.toString();
+                PageContext page = (PageContext) context
+                        .getContext(JspContext.class);
+                int scope = page.getAttributesScope(key);
+                if (scope != 0) {
+                    page.setAttribute(key, value, scope);
+                } else {
+                    page.setAttribute(key, value);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+        }
+
+        return false;
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
+            Object base) {
+
+        PageContext ctxt = (PageContext) context.getContext(JspContext.class);
+        List<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>();
+        Enumeration<String> e;
+        Object value;
+        String name;
+
+        e = ctxt.getAttributeNamesInScope(PageContext.PAGE_SCOPE);
+        while (e.hasMoreElements()) {
+            name = e.nextElement();
+            value = ctxt.getAttribute(name, PageContext.PAGE_SCOPE);
+            FeatureDescriptor descriptor = new FeatureDescriptor();
+            descriptor.setName(name);
+            descriptor.setDisplayName(name);
+            descriptor.setExpert(false);
+            descriptor.setHidden(false);
+            descriptor.setPreferred(true);
+            descriptor.setShortDescription("page scoped attribute");
+            descriptor.setValue("type", value.getClass());
+            descriptor.setValue("resolvableAtDesignTime", Boolean.FALSE);
+            list.add(descriptor);
+        }
+
+        e = ctxt.getAttributeNamesInScope(PageContext.REQUEST_SCOPE);
+        while (e.hasMoreElements()) {
+            name = e.nextElement();
+            value = ctxt.getAttribute(name, PageContext.REQUEST_SCOPE);
+            FeatureDescriptor descriptor = new FeatureDescriptor();
+            descriptor.setName(name);
+            descriptor.setDisplayName(name);
+            descriptor.setExpert(false);
+            descriptor.setHidden(false);
+            descriptor.setPreferred(true);
+            descriptor.setShortDescription("request scope attribute");
+            descriptor.setValue("type", value.getClass());
+            descriptor.setValue("resolvableAtDesignTime", Boolean.FALSE);
+            list.add(descriptor);
+        }
+
+        if (ctxt.getSession() != null) {
+            e = ctxt.getAttributeNamesInScope(PageContext.SESSION_SCOPE);
+            while (e.hasMoreElements()) {
+                name = e.nextElement();
+                value = ctxt.getAttribute(name, PageContext.SESSION_SCOPE);
+                FeatureDescriptor descriptor = new FeatureDescriptor();
+                descriptor.setName(name);
+                descriptor.setDisplayName(name);
+                descriptor.setExpert(false);
+                descriptor.setHidden(false);
+                descriptor.setPreferred(true);
+                descriptor.setShortDescription("session scoped attribute");
+                descriptor.setValue("type", value.getClass());
+                descriptor.setValue("resolvableAtDesignTime", Boolean.FALSE);
+                list.add(descriptor);
+            }
+        }
+
+        e = ctxt.getAttributeNamesInScope(PageContext.APPLICATION_SCOPE);
+        while (e.hasMoreElements()) {
+            name = e.nextElement();
+            value = ctxt.getAttribute(name, PageContext.APPLICATION_SCOPE);
+            FeatureDescriptor descriptor = new FeatureDescriptor();
+            descriptor.setName(name);
+            descriptor.setDisplayName(name);
+            descriptor.setExpert(false);
+            descriptor.setHidden(false);
+            descriptor.setPreferred(true);
+            descriptor.setShortDescription("application scoped attribute");
+            descriptor.setValue("type", value.getClass());
+            descriptor.setValue("resolvableAtDesignTime", Boolean.FALSE);
+            list.add(descriptor);
+        }
+        return list.iterator();
+    }
+
+    @Override
+    public Class<String> getCommonPropertyType(ELContext context, Object base) {
+        if (base == null) {
+            return String.class;
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/VariableResolver.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/VariableResolver.java
new file mode 100644
index 0000000..14f31d9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/VariableResolver.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.jsp.el;
+
+/**
+ * <p>
+ * This class is used to customize the way an ExpressionEvaluator resolves
+ * variable references at evaluation time. For example, instances of this class
+ * can implement their own variable lookup mechanisms, or introduce the notion
+ * of "implicit variables" which override any other variables. An instance of
+ * this class should be passed when evaluating an expression.
+ * </p>
+ * <p>
+ * An instance of this class includes the context against which resolution will
+ * happen
+ * </p>
+ * 
+ * @since 2.0
+ * @deprecated
+ */
+@SuppressWarnings("dep-ann")
+// TCK signature test fails with annotation
+public interface VariableResolver {
+
+    /**
+     * Resolves the specified variable. Returns null if the variable is not
+     * found.
+     * 
+     * @param pName
+     *            the name of the variable to resolve
+     * @return the result of the variable resolution
+     * @throws ELException
+     *             if a failure occurred while trying to resolve the given
+     *             variable
+     */
+    public Object resolveVariable(String pName) throws ELException;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/package.html b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/package.html
new file mode 100644
index 0000000..ba5a629
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/el/package.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+</head>
+<body bgcolor="white">
+
+Classes and interfaces for the JSP 2.0 Expression Language API.
+
+<p>
+The JavaServer Pages(tm) (JSP) 2.0 specification provides a portable
+API for evaluating "EL Expressions".  As of JSP 2.0, EL expressions can
+be placed directly in the template text of JSP pages and tag files.
+<p>
+This package contains a number of classes and interfaces that describe 
+and define programmatic access to the Expression Language evaluator. 
+This API can also be used by an implementation of JSP to evaluate the 
+expressions, but other implementations, like open-coding into Java 
+bytecodes, are allowed.  This package is intended to have no dependencies 
+on other portions of the JSP 2.0 specification. 
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/package.html b/bundles/org.apache.tomcat/src/javax/servlet/jsp/package.html
new file mode 100644
index 0000000..ea458fc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+</head>
+<body bgcolor="white">
+Classes and interfaces for the Core JSP 2.0 API.
+<p>
+The javax.servlet.jsp package contains a number of classes and
+interfaces that describe and define the contracts between a JSP page
+implementation class and the runtime environment provided for an
+instance of such a class by a conforming JSP container.
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/BodyTagProtocol.gif b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/BodyTagProtocol.gif
new file mode 100644
index 0000000..a61e82b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/BodyTagProtocol.gif
Binary files differ
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/IterationTagProtocol.gif b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/IterationTagProtocol.gif
new file mode 100644
index 0000000..c262341
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/IterationTagProtocol.gif
Binary files differ
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/TagProtocol.gif b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/TagProtocol.gif
new file mode 100644
index 0000000..9a1880e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/TagProtocol.gif
Binary files differ
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/VariableInfo-1.gif b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/VariableInfo-1.gif
new file mode 100644
index 0000000..32eabeb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/doc-files/VariableInfo-1.gif
Binary files differ
diff --git a/bundles/org.apache.tomcat/src/javax/xml/ws/WebServiceRef.java b/bundles/org.apache.tomcat/src/javax/xml/ws/WebServiceRef.java
new file mode 100644
index 0000000..6d0d389
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/xml/ws/WebServiceRef.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.xml.ws;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface WebServiceRef {
+    public String name() default "";
+    @SuppressWarnings("rawtypes") // Can't use Class<?> because API needs to match specification
+    public Class type() default java.lang.Object.class;
+    @SuppressWarnings("rawtypes") // Can't use Class<?> because API needs to match specification
+    public Class value() default java.lang.Object.class;
+    public String wsdlLocation() default "";
+    public String mappedName() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/xml/ws/WebServiceRefs.java b/bundles/org.apache.tomcat/src/javax/xml/ws/WebServiceRefs.java
new file mode 100644
index 0000000..851b488
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/xml/ws/WebServiceRefs.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package javax.xml.ws;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface WebServiceRefs {
+    public WebServiceRef[] value();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/CatalinaCluster.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/CatalinaCluster.java
new file mode 100644
index 0000000..dfff7e9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/CatalinaCluster.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+import java.util.Map;
+
+import org.apache.catalina.Cluster;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Valve;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.Member;
+import org.apache.juli.logging.Log;
+
+
+
+/**
+ * A <b>CatalinaCluster</b> interface allows to plug in and out the 
+ * different cluster implementations
+ *
+ * @author Filip Hanik
+ * @version $Id: CatalinaCluster.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+
+public interface CatalinaCluster extends Cluster {
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * Descriptive information about this component implementation.
+     */
+    public String info = "CatalinaCluster/2.0";
+    
+    /**
+     * Start the cluster, the owning container will invoke this
+     * @throws Exception - if failure to start cluster
+     */
+    public void start() throws Exception;
+    
+    /**
+     * Stops the cluster, the owning container will invoke this
+     * @throws LifecycleException
+     */
+    public void stop() throws LifecycleException;
+    
+    /**
+     * Returns the associates logger with this cluster.
+     *
+     * @return Log
+     */
+    public Log getLogger();
+    
+    /**
+     * Sends a message to all the members in the cluster
+     * @param msg ClusterMessage
+     */
+    public void send(ClusterMessage msg);
+    
+    /**
+     * Sends a message to a specific member in the cluster.
+     *
+     * @param msg ClusterMessage
+     * @param dest Member
+     */
+    public void send(ClusterMessage msg, Member dest);
+
+    /**
+     * Returns that cluster has members.
+     */
+    public boolean hasMembers();
+
+    /**
+     * Returns all the members currently participating in the cluster.
+     *
+     * @return Member[]
+     */
+    public Member[] getMembers();
+    
+    /**
+     * Return the member that represents this node.
+     *
+     * @return Member
+     */
+    public Member getLocalMember();
+    
+    public void addValve(Valve valve);
+    
+    public void addClusterListener(ClusterListener listener);
+    
+    public void removeClusterListener(ClusterListener listener);
+    
+    public void setClusterDeployer(ClusterDeployer deployer);
+    
+    public ClusterDeployer getClusterDeployer();
+    
+    /**
+     * @return The map of managers
+     */
+    public Map<String,ClusterManager> getManagers();
+
+    public Manager getManager(String name);
+    public String getManagerName(String name, Manager manager);
+    public Valve[] getValves();
+    
+    public void setChannel(Channel channel);
+    public Channel getChannel();
+    
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterDeployer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterDeployer.java
new file mode 100644
index 0000000..74fed19
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterDeployer.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+/**
+ * A <b>ClusterDeployer</b> interface allows to plug in and out the
+ * different deployment implementations
+ *
+ * @author Filip Hanik
+ * @version $Id: ClusterDeployer.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.tribes.ChannelListener;
+
+public interface ClusterDeployer extends ChannelListener {
+    /**
+     * Descriptive information about this component implementation.
+     */
+    public String info = "ClusterDeployer/1.0";
+    /**
+     * Start the cluster deployer, the owning container will invoke this
+     * @throws Exception - if failure to start cluster
+     */
+    public void start() throws Exception;
+
+    /**
+     * Stops the cluster deployer, the owning container will invoke this
+     * @throws LifecycleException
+     */
+    public void stop() throws LifecycleException;
+
+    /**
+     * Install a new web application, whose web application archive is at the
+     * specified URL, into this container and all the other
+     * members of the cluster with the specified context name.
+     * <p>
+     * If this application is successfully installed locally, 
+     * a ContainerEvent of type
+     * <code>INSTALL_EVENT</code> will be sent to all registered listeners,
+     * with the newly created <code>Context</code> as an argument.
+     *
+     * @param contextName The context name to which this application should
+     *  be installed (must be unique)
+     * @param webapp    A WAR file or unpacked directory structure containing
+     *                  the web application to be installed
+     *
+     * @exception IllegalArgumentException if the specified context name
+     *  is malformed
+     * @exception IllegalStateException if the specified context name
+     *  is already attached to an existing web application
+     * @exception IOException if an input/output error was encountered
+     *  during installation
+     */
+    public void install(String contextName, File webapp) throws IOException;
+
+    /**
+     * Remove an existing web application, attached to the specified context
+     * name.  If this application is successfully removed, a
+     * ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
+     * registered listeners, with the removed <code>Context</code> as
+     * an argument. Deletes the web application war file and/or directory
+     * if they exist in the Host's appBase.
+     *
+     * @param contextName The context name of the application to be removed
+     * @param undeploy boolean flag to remove web application from server
+     *
+     * @exception IllegalArgumentException if the specified context name
+     *  is malformed
+     * @exception IllegalArgumentException if the specified context name does
+     *  not identify a currently installed web application
+     * @exception IOException if an input/output error occurs during
+     *  removal
+     */
+    public void remove(String contextName, boolean undeploy) throws IOException;
+
+    /**
+     * call from container Background Process
+     */
+    public void backgroundProcess();
+    
+    /**
+     * Returns the cluster the cluster deployer is associated with
+     * @return CatalinaCluster
+     */
+    public CatalinaCluster getCluster();
+
+    /**
+     * Associates the cluster deployer with a cluster
+     * @param cluster CatalinaCluster
+     */
+    public void setCluster(CatalinaCluster cluster);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterListener.java
new file mode 100644
index 0000000..aa8974a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterListener.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Member;
+
+
+/**
+ * Receive SessionID cluster change from other backup node after primary session
+ * node is failed.
+ * 
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Id: ClusterListener.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public abstract class ClusterListener implements ChannelListener {
+
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(ClusterListener.class);
+
+    //--Instance Variables--------------------------------------
+
+    /**
+     * The string manager for this package.
+     */
+
+    protected CatalinaCluster cluster = null;
+
+    //--Constructor---------------------------------------------
+
+    public ClusterListener() {
+        // NO-OP
+    }
+    
+    //--Instance Getters/Setters--------------------------------
+    
+    public CatalinaCluster getCluster() {
+        return cluster;
+    }
+
+    public void setCluster(CatalinaCluster cluster) {
+        if (log.isDebugEnabled()) {
+            if (cluster != null)
+                log.debug("add ClusterListener " + this.toString() +
+                        " to cluster" + cluster);
+            else
+                log.debug("remove ClusterListener " + this.toString() +
+                        " from cluster");
+        }
+        this.cluster = cluster;
+    }
+
+    @Override
+    public boolean equals(Object listener) {
+        return super.equals(listener);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    //--Logic---------------------------------------------------
+
+    @Override
+    public final void messageReceived(Serializable msg, Member member) {
+        if ( msg instanceof ClusterMessage ) messageReceived((ClusterMessage)msg);
+    }
+    @Override
+    public final boolean accept(Serializable msg, Member member) {
+        if ( msg instanceof ClusterMessage ) return true;
+        return false;
+    }
+
+
+
+    /**
+     * Callback from the cluster, when a message is received, The cluster will
+     * broadcast it invoking the messageReceived on the receiver.
+     * 
+     * @param msg
+     *            ClusterMessage - the message received from the cluster
+     */
+    public abstract void messageReceived(ClusterMessage msg) ;
+    
+
+    /**
+     * Accept only SessionIDMessages
+     * 
+     * @param msg
+     *            ClusterMessage
+     * @return boolean - returns true to indicate that messageReceived should be
+     *         invoked. If false is returned, the messageReceived method will
+     *         not be invoked.
+     */
+    public abstract boolean accept(ClusterMessage msg) ;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterManager.java
new file mode 100644
index 0000000..1fca879
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterManager.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha;
+
+
+import java.io.IOException;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.tribes.io.ReplicationStream;
+
+
+/**
+ * The common interface used by all cluster manager.
+ * This is so that we can have a more pluggable way
+ * of swapping session managers for different algorithms.
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ */
+public interface ClusterManager extends Manager {
+
+   /**
+    * A message was received from another node, this
+    * is the callback method to implement if you are interested in
+    * receiving replication messages.
+    * @param msg - the message received.
+    */
+   public void messageDataReceived(ClusterMessage msg);
+
+   /**
+    * When the request has been completed, the replication valve
+    * will notify the manager, and the manager will decide whether
+    * any replication is needed or not.
+    * If there is a need for replication, the manager will
+    * create a session message and that will be replicated.
+    * The cluster determines where it gets sent.
+    * @param sessionId - the sessionId that just completed.
+    * @return a SessionMessage to be sent.
+    */
+   public ClusterMessage requestCompleted(String sessionId);
+
+   /**
+    * When the manager expires session not tied to a request.
+    * The cluster will periodically ask for a list of sessions
+    * that should expire and that should be sent across the wire.
+    * @return String[] The invalidated sessions
+    */
+   public String[] getInvalidatedSessions();
+   
+   /**
+    * Return the name of the manager, at host /context name and at engine hostname+/context.
+    * @return String
+    * @since 5.5.10
+    */
+   public String getName();
+   
+   /**
+    * Set the name of the manager, at host /context name and at engine hostname+/context
+    * @param name
+    * @since 5.5.10
+    */
+   public void setName(String name);
+         
+   public CatalinaCluster getCluster();
+
+   public void setCluster(CatalinaCluster cluster);
+
+   public ReplicationStream getReplicationStream(byte[] data) throws IOException;
+
+   public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException;
+   
+   public boolean isNotifyListenersOnReplication();
+
+   public ClusterManager cloneFromTemplate();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterMessage.java
new file mode 100644
index 0000000..2faf20c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterMessage.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Member;
+
+
+/**
+ * @author Filip Hanik
+ * 
+ */
+public interface ClusterMessage extends Serializable {
+    public Member getAddress();
+    public void setAddress(Member member);
+    public String getUniqueId();
+    public void setUniqueId(String id);
+    public long getTimestamp();
+    public void setTimestamp(long timestamp);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterMessageBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterMessageBase.java
new file mode 100644
index 0000000..505087d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterMessageBase.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha;
+
+import org.apache.catalina.tribes.Member;
+
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class ClusterMessageBase implements ClusterMessage {
+    
+    private static final long serialVersionUID = 1L;
+
+    protected transient Member address;
+    private String uniqueId;
+    private long timestamp;
+    public ClusterMessageBase() {
+        // NO-OP
+    }
+
+    /**
+     * getAddress
+     *
+     * @return Member
+     * TODO Implement this org.apache.catalina.ha.ClusterMessage method
+     */
+    @Override
+    public Member getAddress() {
+        return address;
+    }
+
+    @Override
+    public String getUniqueId() {
+        return uniqueId;
+    }
+
+    @Override
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    /**
+     * setAddress
+     *
+     * @param member Member
+     * TODO Implement this org.apache.catalina.ha.ClusterMessage method
+     */
+    @Override
+    public void setAddress(Member member) {
+        this.address = member;
+    }
+
+    @Override
+    public void setUniqueId(String uniqueId) {
+        this.uniqueId = uniqueId;
+    }
+
+    @Override
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterRuleSet.java
new file mode 100644
index 0000000..c70affd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterRuleSet.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a
+ * Cluster definition element.  </p>
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Id: ClusterRuleSet.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+
+public class ClusterRuleSet extends RuleSetBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The matching pattern prefix to use for recognizing our elements.
+     */
+    protected String prefix = null;
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    /**
+     * Construct an instance of this <code>RuleSet</code> with the default
+     * matching pattern prefix.
+     */
+    public ClusterRuleSet() {
+
+        this("");
+
+    }
+
+
+    /**
+     * Construct an instance of this <code>RuleSet</code> with the specified
+     * matching pattern prefix.
+     *
+     * @param prefix Prefix for matching pattern rules (including the
+     *  trailing slash character)
+     */
+    public ClusterRuleSet(String prefix) {
+        super();
+        this.namespaceURI = null;
+        this.prefix = prefix;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * <p>Add the set of Rule instances defined in this RuleSet to the
+     * specified <code>Digester</code> instance, associating them with
+     * our namespace URI (if any).  This method should only be called
+     * by a Digester instance.</p>
+     *
+     * @param digester Digester instance to which the new Rule instances
+     *  should be added.
+     */
+    @Override
+    public void addRuleInstances(Digester digester) {
+        //Cluster configuration start
+        digester.addObjectCreate(prefix + "Manager",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Manager");
+        digester.addSetNext(prefix + "Manager",
+                            "setManagerTemplate",
+                            "org.apache.catalina.ha.ClusterManager");
+
+
+        digester.addObjectCreate(prefix + "Channel",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Channel");
+        digester.addSetNext(prefix + "Channel",
+                            "setChannel",
+                            "org.apache.catalina.tribes.Channel");
+
+
+        String channelPrefix = prefix + "Channel/";
+        { //channel properties
+            digester.addObjectCreate(channelPrefix + "Membership",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(channelPrefix + "Membership");
+            digester.addSetNext(channelPrefix + "Membership",
+                                "setMembershipService",
+                                "org.apache.catalina.tribes.MembershipService");
+
+            digester.addObjectCreate(channelPrefix + "Sender",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(channelPrefix + "Sender");
+            digester.addSetNext(channelPrefix + "Sender",
+                                "setChannelSender",
+                                "org.apache.catalina.tribes.ChannelSender");
+
+            digester.addObjectCreate(channelPrefix + "Sender/Transport",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(channelPrefix + "Sender/Transport");
+            digester.addSetNext(channelPrefix + "Sender/Transport",
+                                "setTransport",
+                                "org.apache.catalina.tribes.transport.MultiPointSender");
+
+
+            digester.addObjectCreate(channelPrefix + "Receiver",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(channelPrefix + "Receiver");
+            digester.addSetNext(channelPrefix + "Receiver",
+                                "setChannelReceiver",
+                                "org.apache.catalina.tribes.ChannelReceiver");
+
+            digester.addObjectCreate(channelPrefix + "Interceptor",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(channelPrefix + "Interceptor");
+            digester.addSetNext(channelPrefix + "Interceptor",
+                                "addInterceptor",
+                                "org.apache.catalina.tribes.ChannelInterceptor");
+
+            
+            digester.addObjectCreate(channelPrefix + "Interceptor/Member",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(channelPrefix + "Interceptor/Member");
+            digester.addSetNext(channelPrefix + "Interceptor/Member",
+                                "addStaticMember",
+                                "org.apache.catalina.tribes.Member");
+        }
+
+        digester.addObjectCreate(prefix + "Valve",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Valve");
+        digester.addSetNext(prefix + "Valve",
+                            "addValve",
+                            "org.apache.catalina.Valve");
+        
+        digester.addObjectCreate(prefix + "Deployer",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Deployer");
+        digester.addSetNext(prefix + "Deployer",
+                            "setClusterDeployer",
+                            "org.apache.catalina.ha.ClusterDeployer");
+        
+        digester.addObjectCreate(prefix + "Listener",
+                null, // MUST be specified in the element
+                "className");
+        digester.addSetProperties(prefix + "Listener");
+        digester.addSetNext(prefix + "Listener",
+                            "addLifecycleListener",
+                            "org.apache.catalina.LifecycleListener");
+        
+        digester.addObjectCreate(prefix + "ClusterListener",
+                null, // MUST be specified in the element
+                "className");
+        digester.addSetProperties(prefix + "ClusterListener");
+        digester.addSetNext(prefix + "ClusterListener",
+                            "addClusterListener",
+                            "org.apache.catalina.ha.ClusterListener");
+        //Cluster configuration end
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterSession.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterSession.java
new file mode 100644
index 0000000..eb124bb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterSession.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Session;
+
+public interface ClusterSession extends Session, HttpSession {
+   /**
+    * returns true if this session is the primary session, if that is the
+    * case, the manager can expire it upon timeout.
+    * @return True if this session is primary
+    */
+   public boolean isPrimarySession();
+
+   /**
+    * Sets whether this is the primary session or not.
+    * @param primarySession Flag value
+    */
+   public void setPrimarySession(boolean primarySession);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterValve.java
new file mode 100644
index 0000000..fde9b88
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/ClusterValve.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha;
+
+import org.apache.catalina.Valve;
+
+/**
+ * Cluster valves are a simple extension to the Tomcat valve architecture
+ * with a small addition of being able to reference the cluster component in the container it sits in.
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Id: ClusterValve.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public interface ClusterValve extends Valve{
+    /**
+     * Returns the cluster the cluster deployer is associated with
+     * @return CatalinaCluster
+     */
+    public CatalinaCluster getCluster();
+
+    /**
+     * Associates the cluster deployer with a cluster
+     * @param cluster CatalinaCluster
+     */
+    public void setCluster(CatalinaCluster cluster);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java
new file mode 100644
index 0000000..4b51d50
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java
@@ -0,0 +1,434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.authenticator;
+
+
+import java.security.Principal;
+
+import org.apache.catalina.Cluster;
+import org.apache.catalina.Container;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.authenticator.SingleSignOn;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+
+/**
+ * A <strong>Valve</strong> that supports a "single sign on" user experience on
+ * each nodes of a cluster, where the security identity of a user who successfully
+ * authenticates to one web application is propagated to other web applications and
+ * to other nodes cluster in the same security domain.  For successful use, the following
+ * requirements must be met:
+ * <ul>
+ * <li>This Valve must be configured on the Container that represents a
+ *     virtual host (typically an implementation of <code>Host</code>).</li>
+ * <li>The <code>Realm</code> that contains the shared user and role
+ *     information must be configured on the same Container (or a higher
+ *     one), and not overridden at the web application level.</li>
+ * <li>The web applications themselves must use one of the standard
+ *     Authenticators found in the
+ *     <code>org.apache.catalina.authenticator</code> package.</li>
+ * </ul>
+ *
+ * @author Fabien Carrion
+ */
+
+public class ClusterSingleSignOn
+    extends SingleSignOn {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Descriptive information about this Valve implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.ha.authenticator.ClusterSingleSignOn";
+
+    protected int messageNumber = 0;
+
+    private ClusterSingleSignOnListener clusterSSOListener = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+    private CatalinaCluster cluster = null;
+
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    public CatalinaCluster getCluster() {
+
+        return cluster;
+
+    }
+
+    public void setCluster(CatalinaCluster cluster) {
+
+        this.cluster = cluster;
+
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Start this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void startInternal() throws LifecycleException {
+        
+        clusterSSOListener = new ClusterSingleSignOnListener();
+        clusterSSOListener.setClusterSSO(this);
+
+        // Load the cluster component, if any
+        try {
+            //the channel is already running
+            Cluster cluster = getCluster();
+            // stop remove cluster binding
+            if(cluster == null) {
+                Container host = getContainer();
+                if(host != null && host instanceof Host) {
+                    cluster = host.getCluster();
+                    if(cluster != null && cluster instanceof CatalinaCluster) {
+                        setCluster((CatalinaCluster) cluster);
+                        getCluster().addClusterListener(clusterSSOListener);
+                    } else {
+                        Container engine = host.getParent();
+                        if(engine != null && engine instanceof Engine) {
+                            cluster = engine.getCluster();
+                            if(cluster != null && cluster instanceof CatalinaCluster) {
+                                setCluster((CatalinaCluster) cluster);
+                                getCluster().addClusterListener(clusterSSOListener);
+                            }
+                        } else {
+                            cluster = null;
+                        }
+                    }
+                }
+            }
+            if (cluster == null) {
+                throw new LifecycleException(
+                        "There is no Cluster for ClusterSingleSignOn");
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            throw new LifecycleException(
+                    "ClusterSingleSignOn exception during clusterLoad " + t);
+        }
+
+        super.startInternal();
+    }
+
+
+    /**
+     * Stop this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void stopInternal() throws LifecycleException {
+
+        super.stopInternal();
+
+        if (getCluster() != null) {
+            getCluster().removeClusterListener(clusterSSOListener);
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Notify the cluster of the addition of a Session to
+     * an SSO session and associate the specified single
+     * sign on identifier with the specified Session on the
+     * local node.
+     *
+     * @param ssoId Single sign on identifier
+     * @param session Session to be associated
+     */
+    @Override
+    protected void associate(String ssoId, Session session) {
+
+        if (cluster != null) {
+            messageNumber++;
+            SingleSignOnMessage msg =
+                new SingleSignOnMessage(cluster.getLocalMember(),
+                                        ssoId, session.getId());
+            Manager mgr = session.getManager();
+            if ((mgr != null) && (mgr instanceof ClusterManager))
+                msg.setContextName(((ClusterManager) mgr).getName());
+
+            msg.setAction(SingleSignOnMessage.ADD_SESSION);
+
+            cluster.send(msg);
+
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("SingleSignOnMessage Send with action "
+                                   + msg.getAction());
+        }
+
+        associateLocal(ssoId, session);
+
+    }
+
+    protected void associateLocal(String ssoId, Session session) {
+
+        super.associate(ssoId, session);
+
+    }
+
+    /**
+     * Notify the cluster of the removal of a Session from an
+     * SSO session and deregister the specified session. If it is the last
+     * session, then also get rid of the single sign on identifier on the
+     * local node.
+     *
+     * @param ssoId Single sign on identifier
+     * @param session Session to be deregistered
+     */
+    @Override
+    protected void deregister(String ssoId, Session session) {
+
+        if (cluster != null) {
+            messageNumber++;
+            SingleSignOnMessage msg =
+                new SingleSignOnMessage(cluster.getLocalMember(),
+                                        ssoId, session.getId());
+            Manager mgr = session.getManager();
+            if ((mgr != null) && (mgr instanceof ClusterManager))
+                msg.setContextName(((ClusterManager) mgr).getName());
+
+            msg.setAction(SingleSignOnMessage.DEREGISTER_SESSION);
+
+            cluster.send(msg);
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("SingleSignOnMessage Send with action "
+                                   + msg.getAction());
+        }
+
+        deregisterLocal(ssoId, session);
+
+    }
+
+    protected void deregisterLocal(String ssoId, Session session) {
+
+        super.deregister(ssoId, session);
+
+    }
+
+    /**
+     * Notifies the cluster that a single sign on session
+     * has been terminated due to a user logout, deregister
+     * the specified single sign on identifier, and invalidate
+     * any associated sessions on the local node.
+     *
+     * @param ssoId Single sign on identifier to deregister
+     */
+    @Override
+    protected void deregister(String ssoId) {
+
+        if (cluster != null) {
+            messageNumber++;
+            SingleSignOnMessage msg =
+                new SingleSignOnMessage(cluster.getLocalMember(),
+                                        ssoId, null);
+            msg.setAction(SingleSignOnMessage.LOGOUT_SESSION);
+
+            cluster.send(msg);
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("SingleSignOnMessage Send with action "
+                                   + msg.getAction());
+        }
+
+        deregisterLocal(ssoId);
+
+    }
+
+    protected void deregisterLocal(String ssoId) {
+
+        super.deregister(ssoId);
+
+    }
+
+    /**
+     * Notifies the cluster of the creation of a new SSO entry
+     * and register the specified Principal as being associated
+     * with the specified value for the single sign on identifier.
+     *
+     * @param ssoId Single sign on identifier to register
+     * @param principal Associated user principal that is identified
+     * @param authType Authentication type used to authenticate this
+     *  user principal
+     * @param username Username used to authenticate this user
+     * @param password Password used to authenticate this user
+     */
+    @Override
+    protected void register(String ssoId, Principal principal, String authType,
+                  String username, String password) {
+
+        if (cluster != null) {
+            messageNumber++;
+            SingleSignOnMessage msg =
+                new SingleSignOnMessage(cluster.getLocalMember(),
+                                        ssoId, null);
+            msg.setAction(SingleSignOnMessage.REGISTER_SESSION);
+            msg.setAuthType(authType);
+            msg.setUsername(username);
+            msg.setPassword(password);
+
+            cluster.send(msg);
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("SingleSignOnMessage Send with action "
+                                   + msg.getAction());
+        }
+
+        registerLocal(ssoId, principal, authType, username, password);
+
+    }
+
+    protected void registerLocal(String ssoId, Principal principal, String authType,
+                  String username, String password) {
+
+        super.register(ssoId, principal, authType, username, password);
+
+    }
+
+
+    /**
+     * Notifies the cluster of an update of the security credentials
+     * associated with an SSO session. Updates any <code>SingleSignOnEntry</code>
+     * found under key <code>ssoId</code> with the given authentication data.
+     * <p>
+     * The purpose of this method is to allow an SSO entry that was
+     * established without a username/password combination (i.e. established
+     * following DIGEST or CLIENT-CERT authentication) to be updated with
+     * a username and password if one becomes available through a subsequent
+     * BASIC or FORM authentication.  The SSO entry will then be usable for
+     * reauthentication.
+     * <p>
+     * <b>NOTE:</b> Only updates the SSO entry if a call to
+     * <code>SingleSignOnEntry.getCanReauthenticate()</code> returns
+     * <code>false</code>; otherwise, it is assumed that the SSO entry already
+     * has sufficient information to allow reauthentication and that no update
+     * is needed.
+     *
+     * @param ssoId     identifier of Single sign to be updated
+     * @param principal the <code>Principal</code> returned by the latest
+     *                  call to <code>Realm.authenticate</code>.
+     * @param authType  the type of authenticator used (BASIC, CLIENT-CERT,
+     *                  DIGEST or FORM)
+     * @param username  the username (if any) used for the authentication
+     * @param password  the password (if any) used for the authentication
+     */
+    @Override
+    protected void update(String ssoId, Principal principal, String authType,
+                          String username, String password) {
+
+        if (cluster != null) {
+            messageNumber++;
+            SingleSignOnMessage msg =
+                new SingleSignOnMessage(cluster.getLocalMember(),
+                                        ssoId, null);
+            msg.setAction(SingleSignOnMessage.UPDATE_SESSION);
+            msg.setAuthType(authType);
+            msg.setUsername(username);
+            msg.setPassword(password);
+
+            cluster.send(msg);
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("SingleSignOnMessage Send with action "
+                                   + msg.getAction());
+        }
+
+        updateLocal(ssoId, principal, authType, username, password);
+
+    }
+
+    protected void updateLocal(String ssoId, Principal principal, String authType,
+                          String username, String password) {
+
+        super.update(ssoId, principal, authType, username, password);
+
+    }
+
+
+    /**
+     * Remove a single Session from a SingleSignOn and notify the cluster
+     * of the removal. Called when a session is timed out and no longer active.
+     *
+     * @param ssoId Single sign on identifier from which to remove the session.
+     * @param session the session to be removed.
+     */
+    @Override
+    protected void removeSession(String ssoId, Session session) {
+
+        if (cluster != null) {
+            messageNumber++;
+            SingleSignOnMessage msg =
+                new SingleSignOnMessage(cluster.getLocalMember(),
+                                        ssoId, session.getId());
+
+            Manager mgr = session.getManager();
+            if ((mgr != null) && (mgr instanceof ClusterManager))
+                msg.setContextName(((ClusterManager) mgr).getName());
+
+            msg.setAction(SingleSignOnMessage.REMOVE_SESSION);
+
+            cluster.send(msg);
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("SingleSignOnMessage Send with action "
+                                   + msg.getAction());
+        }
+
+        removeSessionLocal(ssoId, session);
+    }
+
+    protected void removeSessionLocal(String ssoId, Session session) {
+
+        super.removeSession(ssoId, session);
+        
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java
new file mode 100644
index 0000000..e14a38e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.authenticator;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.ha.ClusterListener;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Receive replicated SingleSignOnMessage form other cluster node.
+ * 
+ * @author Fabien Carrion
+ */
+public class ClusterSingleSignOnListener extends ClusterListener {
+ 
+    private static final Log log =
+        LogFactory.getLog(ClusterSingleSignOnListener.class);
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info = "org.apache.catalina.ha.authenticator.ClusterSingleSignOnListener/1.0";
+
+    // ------------------------------------------------------------- Properties
+
+    private ClusterSingleSignOn clusterSSO = null;
+
+
+    //--Constructor---------------------------------------------
+
+    public ClusterSingleSignOnListener() {
+        // NO-OP
+    }
+
+    //--Logic---------------------------------------------------
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    public ClusterSingleSignOn getClusterSSO() {
+
+        return clusterSSO;
+
+    }
+
+    public void setClusterSSO(ClusterSingleSignOn clusterSSO) {
+
+        this.clusterSSO = clusterSSO;
+
+    }
+
+
+    /**
+     * Callback from the cluster, when a message is received, The cluster will
+     * broadcast it invoking the messageReceived on the receiver.
+     * 
+     * @param myobj
+     *            ClusterMessage - the message received from the cluster
+     */
+    @Override
+    public void messageReceived(ClusterMessage myobj) {
+        if (myobj != null && myobj instanceof SingleSignOnMessage) {
+            SingleSignOnMessage msg = (SingleSignOnMessage) myobj;
+            int action = msg.getAction();
+            Session session = null;
+
+            if (log.isDebugEnabled())
+                log.debug("SingleSignOnMessage Received with action "
+                          + msg.getAction());
+
+            switch(action) {
+            case SingleSignOnMessage.ADD_SESSION:
+                session = getSession(msg.getSessionId(),
+                                     msg.getContextName());
+                if (session != null)
+                    clusterSSO.associateLocal(msg.getSsoId(), session);
+                break;
+            case SingleSignOnMessage.DEREGISTER_SESSION:
+                session = getSession(msg.getSessionId(),
+                                     msg.getContextName());
+                if (session != null)
+                    clusterSSO.deregisterLocal(msg.getSsoId(), session);
+                break;
+            case SingleSignOnMessage.LOGOUT_SESSION:
+                clusterSSO.deregisterLocal(msg.getSsoId());
+                break;
+            case SingleSignOnMessage.REGISTER_SESSION:
+                clusterSSO.registerLocal(msg.getSsoId(), null, msg.getAuthType(),
+                                         msg.getUsername(), msg.getPassword());
+                break;
+            case SingleSignOnMessage.UPDATE_SESSION:
+                clusterSSO.updateLocal(msg.getSsoId(), null, msg.getAuthType(),
+                                       msg.getUsername(), msg.getPassword());
+                break;
+            case SingleSignOnMessage.REMOVE_SESSION:
+                session = getSession(msg.getSessionId(),
+                                     msg.getContextName());
+                if (session != null)
+                    clusterSSO.removeSessionLocal(msg.getSsoId(), session);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Accept only SingleSignOnMessage
+     * 
+     * @param msg
+     *            ClusterMessage
+     * @return boolean - returns true to indicate that messageReceived should be
+     *         invoked. If false is returned, the messageReceived method will
+     *         not be invoked.
+     */
+    @Override
+    public boolean accept(ClusterMessage msg) {
+        return (msg instanceof SingleSignOnMessage);
+    }
+
+
+    private Session getSession(String sessionId, String ctxname) {
+        
+        Map<String,ClusterManager> managers = clusterSSO.getCluster().getManagers();
+        Session session = null;
+
+        if (ctxname == null) {
+            for (Map.Entry<String, ClusterManager> entry : managers.entrySet()) {
+                if (entry.getValue() != null) {
+                    try {
+                        session = entry.getValue().findSession(sessionId);
+                    } catch (IOException io) {
+                        log.error("Session doesn't exist:" + io);
+                    }
+                    return session;
+                }
+                //this happens a lot before the system has started
+                // up
+                if (log.isDebugEnabled())
+                    log.debug("Context manager doesn't exist:"
+                              + entry.getKey());
+            }
+        } else {
+            ClusterManager mgr = managers.get(ctxname);
+            if (mgr != null) {
+                try {
+                    session = mgr.findSession(sessionId);
+                } catch (IOException io) {
+                    log.error("Session doesn't exist:" + io);
+                }
+                return session;
+            } else if (log.isErrorEnabled())
+                log.error("Context manager doesn't exist:" + ctxname);
+        }
+
+        return null;
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java
new file mode 100644
index 0000000..2abfeb2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.authenticator;
+
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+
+/**
+ * Contains the SingleSignOn data, read and written by the ClusterSingleSignOn
+ * @author Fabien Carrion
+ */
+
+public class SingleSignOnMessage implements ClusterMessage {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final int ADD_SESSION = 1;
+    public static final int DEREGISTER_SESSION = 2;
+    public static final int LOGOUT_SESSION = 3;
+    public static final int REGISTER_SESSION = 4;
+    public static final int UPDATE_SESSION = 5;
+    public static final int REMOVE_SESSION = 6;
+
+    private int action = -1;
+    private String ssoId = null;
+    private String ctxname = null;
+    private String sessionId = null;
+    private String authType = null;
+    private String password = null;
+    private String username = null;
+
+    private Member address = null;
+    private long timestamp = 0;
+    private String uniqueId = null;
+
+    public SingleSignOnMessage(Member source,
+                               String ssoId,
+                               String sessionId) {
+        this.address = source;
+        this.ssoId = ssoId;
+        this.sessionId = sessionId;
+    }
+    
+    /**
+     * Get the address that this message originated from.  This would be set
+     * if the message was being relayed from a host other than the one
+     * that originally sent it.
+     */
+    @Override
+    public Member getAddress() {
+        return address;
+    }
+
+    /**
+     * Called by the cluster before sending it to the other
+     * nodes.
+     *
+     * @param member Member
+     */
+    @Override
+    public void setAddress(Member member) {
+        this.address = member;
+    }
+
+    /**
+     * Timestamp message.
+     *
+     * @return long
+     */
+    @Override
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    /**
+     * Called by the cluster before sending out
+     * the message.
+     *
+     * @param timestamp The timestamp
+     */
+    @Override
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    /**
+     * Each message must have a unique ID, in case of using async replication,
+     * and a smart queue, this id is used to replace messages not yet sent.
+     *
+     * @return String
+     */
+    @Override
+    public String getUniqueId() {
+        if (this.uniqueId != null)
+            return this.uniqueId;
+        StringBuilder result = new StringBuilder(getSsoId());
+        result.append("#-#");
+        result.append(System.currentTimeMillis());
+        return result.toString();
+    }
+
+    @Override
+    public void setUniqueId(String uniqueId) {
+        this.uniqueId = uniqueId;
+    }
+
+    public int getAction() {
+        return action;
+    }
+
+    public void setAction(int action) {
+        this.action = action;
+    }
+
+    public String getSsoId() {
+        return ssoId;
+    }
+
+    public void setSsoId(String ssoId) {
+        this.ssoId = ssoId;
+    }
+
+    public String getContextName() {
+        return ctxname;
+    }
+
+    public void setContextName(String ctxname) {
+        this.ctxname = ctxname;
+    }
+
+    public String getSessionId() {
+        return sessionId;
+    }
+
+    public void setSessionId(String sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    public String getAuthType() {
+        return authType;
+    }
+
+    public void setAuthType(String authType) {
+        this.authType = authType;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SingleSignOnMessage[action=");
+        sb.append(getAction()).append(", ssoId=").append(getSsoId());
+        sb.append(", sessionId=").append(getSessionId()).append(", username=");
+        sb.append(getUsername()).append("]");
+        return (sb.toString());
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml
new file mode 100644
index 0000000..0636f95
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<mbeans-descriptors>
+  <mbean
+    name="ClusterSingleSignOn"
+    description="A Valve that supports a 'single signon' user experience on a whole cluster"
+    domain="Catalina"
+    group="Valve"
+    type="org.apache.catalina.cluster.authenticator.ClusterSingleSignOn">
+    <attribute
+      name="className"
+      description="Fully qualified class name of the managed object"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="requireReauthentication"
+      description="Should we attempt to reauthenticate each request against the security Realm?"
+      type="boolean"/>
+    <attribute
+      name="cookieDomain"
+      description="(Optiona) Domain to be used by sso cookies"
+      type="java.lang.String"/>
+  </mbean>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/CollectedInfo.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/CollectedInfo.java
new file mode 100644
index 0000000..c4da0f5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/CollectedInfo.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.backend;
+
+/* for MBean to read ready and busy */
+
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+
+import org.apache.tomcat.util.modeler.Registry;
+
+/*
+ * Listener to provider informations to mod_heartbeat.c
+ * *msg_format = "v=%u&ready=%u&busy=%u"; (message to send).
+ * send the muticast message using the format...
+ * what about the bind(IP. port) only IP makes sense (for the moment).
+ * BTW:v  = version :-)
+ */
+public class CollectedInfo {
+
+    /* Collect info via JMX */
+    protected MBeanServer mBeanServer = null;
+    protected ObjectName objName = null;
+
+    int ready;
+    int busy;
+
+    int port = 0;
+    String host = null;
+
+    public CollectedInfo(String host, int port) throws Exception {
+        init(host, port);
+    }
+    public void init(String host, int port) throws Exception {
+        int iport = 0;
+        String shost = null;
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+        String onStr = "*:type=ThreadPool,*";
+        ObjectName objectName = new ObjectName(onStr);
+        Set<ObjectInstance> set = mBeanServer.queryMBeans(objectName, null);
+        Iterator<ObjectInstance> iterator = set.iterator();
+        while (iterator.hasNext()) {
+            ObjectInstance oi = iterator.next();
+            objName = oi.getObjectName();
+            String name = objName.getKeyProperty("name");
+              
+            /* Name are:
+             * http-8080
+             * jk-10.33.144.3-8009
+             * jk-jfcpc%2F10.33.144.3-8009
+             */
+            String [] elenames = name.split("-");
+            String sport = elenames[elenames.length-1];
+            iport = Integer.parseInt(sport);
+            String [] shosts = elenames[1].split("%2F");
+            shost = shosts[0];
+
+            if (port==0 && host==null)
+                  break; /* Take the first one */
+            if (host==null && iport==port)
+                break; /* Only port done */
+            if (shost.compareTo(host) == 0)
+                break; /* Done port and host are the expected ones */
+        }
+        if (objName == null)
+            throw(new Exception("Can't find connector for " + host + ":" + port));
+        this.port = iport;
+        this.host = shost;
+        
+    }
+
+    public void refresh() throws Exception {
+        if (mBeanServer == null || objName == null) {
+            throw(new Exception("Not initialized!!!"));
+        }
+        Integer imax = (Integer) mBeanServer.getAttribute(objName, "maxThreads");
+
+        // the currentThreadCount could be 0 before the threads are created...
+        // Integer iready = (Integer) mBeanServer.getAttribute(objName, "currentThreadCount");
+
+        Integer ibusy  = (Integer) mBeanServer.getAttribute(objName, "currentThreadsBusy");
+
+        busy = ibusy.intValue();
+        ready = imax.intValue() - ibusy.intValue();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/HeartbeatListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/HeartbeatListener.java
new file mode 100644
index 0000000..9bfd426
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/HeartbeatListener.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.backend;
+
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/*
+ * Listener to provider informations to mod_heartbeat.c
+ * *msg_format = "v=%u&ready=%u&busy=%u"; (message to send).
+ * send the muticast message using the format...
+ * what about the bind(IP. port) only IP makes sense (for the moment).
+ * BTW:v  = version :-)
+ */
+public class HeartbeatListener
+    implements LifecycleListener, ContainerListener {
+
+    private static final Log log = LogFactory.getLog(HeartbeatListener.class);
+
+    /* To allow to select the connector */
+    int port = 0;
+    String host = null;
+    public void setHost(String host) { this.host = host; }
+    public void setPort(int port) { this.port = port; }
+
+    /* for multicasting stuff */
+    String ip = "224.0.1.105"; /* Multicast IP */
+    int multiport = 23364;     /* Multicast Port */
+    int ttl = 16;
+
+    public void setGroup(String ip) { this.ip = ip; }
+    public String getGroup() { return ip; }
+    public void setMultiport(int multiport) { this.multiport = multiport; }
+    public int getMultiport() { return multiport; }
+    public void setTtl(int ttl) { this.ttl = ttl; }
+    public int getTtl() { return ttl; }
+
+    /**
+     * Proxy list, format "address:port,address:port".
+     */
+    protected String proxyList = null;
+    public String getProxyList() { return proxyList; }
+    public void setProxyList(String proxyList) { this.proxyList = proxyList; }
+
+    /**
+     * URL prefix.
+     */
+    protected String proxyURL = "/HeartbeatListener";
+    public String getProxyURL() { return proxyURL; }
+    public void setProxyURL(String proxyURL) { this.proxyURL = proxyURL; }
+
+    private CollectedInfo coll = null;
+
+    private Sender sender = null;
+
+    @Override
+    public void containerEvent(ContainerEvent event) {
+    }
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        if (Lifecycle.PERIODIC_EVENT.equals(event.getType())) {
+            if (sender == null) {
+                if (proxyList == null)
+                    sender = new MultiCastSender();
+                else
+                    sender = new TcpSender();
+            }
+
+            /* Read busy and ready */
+            if (coll == null) {
+                try {
+                    coll = new CollectedInfo(host, port);
+                    this.port = coll.port;
+                    this.host = coll.host;
+                } catch (Exception ex) {
+                    log.error("Unable to initialize info collection: " + ex);
+                    coll = null;
+                    return;
+                } 
+            }
+
+            /* Start or restart sender */
+            try {
+                sender.init(this);
+            } catch (Exception ex) {
+                log.error("Unable to initialize Sender: " + ex);
+                sender = null;
+                return;
+            }
+
+            /* refresh the connector information and send it */
+            try {
+                coll.refresh();
+            } catch (Exception ex) {
+                log.error("Unable to collect load information: " + ex);
+                coll = null;
+                return;
+            }
+            String output = "v=1&ready=" + coll.ready + "&busy=" + coll.busy +
+                    "&port=" + port;
+            try {
+                sender.send(output);
+            } catch (Exception ex) {
+                log.error("Unable to send colllected load information: " + ex);
+            }
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/MultiCastSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/MultiCastSender.java
new file mode 100644
index 0000000..bbb5612
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/MultiCastSender.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.backend;
+
+import java.io.UnsupportedEncodingException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/*
+ * Sender to proxies using multicast socket.
+ */
+public class MultiCastSender
+    implements Sender {
+
+    private static final Log log = LogFactory.getLog(HeartbeatListener.class);
+
+    HeartbeatListener config = null;
+
+    /* for multicasting stuff */
+    MulticastSocket s = null;
+    InetAddress group = null;
+
+    @Override
+    public void init(HeartbeatListener config) throws Exception {
+        this.config = config;
+    }
+
+    @Override
+    public int send(String mess) throws Exception {
+        if (s == null) {
+            try {
+                group = InetAddress.getByName(config.getGroup());
+                if (config.host != null) {
+                    InetAddress addr =  InetAddress.getByName(config.host);
+                    InetSocketAddress addrs = new InetSocketAddress(addr, config.getMultiport());
+                    s = new MulticastSocket(addrs);
+                } else
+                    s = new MulticastSocket(config.getMultiport());
+          
+                s.setTimeToLive(config.getTtl());
+                s.joinGroup(group);
+            } catch (Exception ex) {
+                log.error("Unable to use multicast: " + ex);
+                s = null;
+                return -1;
+            } 
+        }
+
+        byte[] buf;
+        try {
+            buf = mess.getBytes("US-ASCII");
+        } catch (UnsupportedEncodingException ex) {
+            buf = mess.getBytes();
+        }
+        DatagramPacket data = new DatagramPacket(buf, buf.length, group, config.getMultiport());
+        try {
+            s.send(data);
+        } catch (Exception ex) {
+            log.error("Unable to send colllected load information: " + ex);
+            s.close();
+            s = null;
+            return -1;
+        }
+        return 0;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/Proxy.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/Proxy.java
new file mode 100644
index 0000000..e6dfc4b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/Proxy.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.backend;
+
+import java.net.InetAddress;
+
+/*
+ * This class represents a front-end httpd server.
+ *
+ */
+public class Proxy {
+
+  protected enum State { OK, ERROR, DOWN }
+
+  public InetAddress address = null;
+  public int port = 80;
+  public State state = State.OK;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/Sender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/Sender.java
new file mode 100644
index 0000000..02e3462
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/Sender.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.backend;
+
+/*
+ * Interface to send data to proxies
+ *
+ */
+public interface Sender {
+
+  /**
+   * Set the configuration parameters
+   */
+  public void init(HeartbeatListener config) throws Exception;
+
+  /**
+   * Send the message to the proxies
+   */
+  public int send(String mess) throws Exception;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/TcpSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/TcpSender.java
new file mode 100644
index 0000000..2ee0a20
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/backend/TcpSender.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.ha.backend;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.StringTokenizer;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/*
+ * Sender to proxies using multicast socket.
+ */
+public class TcpSender
+    implements Sender {
+
+    private static final Log log = LogFactory.getLog(HeartbeatListener.class);
+
+    HeartbeatListener config = null;
+
+    /**
+     * Proxies.
+     */
+    protected Proxy[] proxies = null;
+
+
+    /**
+     * Active connections.
+     */
+
+    protected Socket[] connections = null;
+    protected BufferedReader[] connectionReaders = null;
+    protected BufferedWriter[] connectionWriters = null;
+
+
+    @Override
+    public void init(HeartbeatListener config) throws Exception {
+        this.config = config;
+        StringTokenizer tok = new StringTokenizer(config.getProxyList(), ",");
+        proxies = new Proxy[tok.countTokens()];
+        int i = 0;
+        while (tok.hasMoreTokens()) {
+            String token = tok.nextToken().trim();
+            int pos = token.indexOf(':');
+            if (pos <=0)
+                throw new Exception("bad ProxyList");
+            proxies[i] = new Proxy();
+            proxies[i].port = Integer.parseInt(token.substring(pos + 1));
+            try {
+                 proxies[i].address = InetAddress.getByName(token.substring(0, pos));
+            } catch (Exception e) {
+                throw new Exception("bad ProxyList");
+            }
+            i++;
+        }
+        connections = new Socket[proxies.length];
+        connectionReaders = new BufferedReader[proxies.length];
+        connectionWriters = new BufferedWriter[proxies.length];
+
+    }
+
+    @Override
+    public int send(String mess) throws Exception {
+        if (connections == null) {
+            log.error("Not initialized");
+            return -1;
+        }
+        String requestLine = "POST " + config.getProxyURL() + " HTTP/1.0";
+
+        for (int i = 0; i < connections.length; i++) {
+            if (connections[i] == null) {
+                try {
+                    if (config.host != null) {
+                        connections[i] = new Socket();
+                        InetAddress addr =  InetAddress.getByName(config.host);
+                        InetSocketAddress addrs = new InetSocketAddress(addr, 0);
+                        connections[i].setReuseAddress(true);
+                        connections[i].bind(addrs);
+                        addrs = new InetSocketAddress(proxies[i].address, proxies[i].port);
+                        connections[i].connect(addrs);
+                    } else 
+                        connections[i] = new Socket(proxies[i].address, proxies[i].port);
+                    connectionReaders[i] = new BufferedReader(new InputStreamReader(connections[i].getInputStream()));
+                    connectionWriters[i] = new BufferedWriter(new OutputStreamWriter(connections[i].getOutputStream()));
+                } catch (Exception ex) {
+                    log.error("Unable to connect to proxy: " + ex);
+                    close(i);
+                } 
+            }
+            if (connections[i] == null)
+                continue; // try next proxy in the list
+            BufferedWriter writer = connectionWriters[i];
+            try {
+                writer.write(requestLine); 
+                writer.write("\r\n");
+                writer.write("Content-Length: " + mess.length() + "\r\n");
+                writer.write("User-Agent: HeartbeatListener/1.0\r\n");
+                writer.write("Connection: Keep-Alive\r\n");
+                writer.write("\r\n");
+                writer.write(mess);
+                writer.write("\r\n");
+                writer.flush();
+            } catch (Exception ex) {
+                log.error("Unable to send collected load information to proxy: " + ex);
+                close(i);
+            } 
+            if (connections[i] == null)
+                continue; // try next proxy in the list
+            
+            /* Read httpd answer */
+            String responseStatus = connectionReaders[i].readLine();
+            if (responseStatus == null) {
+                log.error("Unable to read response from proxy");
+                close(i);
+                continue;
+            } else {
+                responseStatus = responseStatus.substring(responseStatus.indexOf(' ') + 1, responseStatus.indexOf(' ', responseStatus.indexOf(' ') + 1));
+                int status = Integer.parseInt(responseStatus);
+                if (status != 200) {
+                    log.error("Status is " + status);
+                    close(i);
+                    continue;
+                }
+
+                // read all the headers.
+                String header = connectionReaders[i].readLine();
+                int contentLength = 0;
+                while (!"".equals(header)) {
+                    int colon = header.indexOf(':');
+                    String headerName = header.substring(0, colon).trim();
+                    String headerValue = header.substring(colon + 1).trim();
+                    if ("content-length".equalsIgnoreCase(headerName)) {
+                        contentLength = Integer.parseInt(headerValue);
+                    }
+                    header = connectionReaders[i].readLine();
+                }
+                if (contentLength > 0) {
+                    char[] buf = new char[512];
+                    while (contentLength > 0) {
+                        int thisTime = (contentLength > buf.length) ? buf.length : contentLength;
+                        int n = connectionReaders[i].read(buf, 0, thisTime);
+                        if (n <= 0) {
+                            log.error("Read content failed");
+                            close(i);
+                            break;
+                        } else {
+                            contentLength -= n;
+                        }
+                   }
+                }
+            }
+               
+        }
+
+        return 0;
+    }
+
+    /**
+     * Close connection.
+     */
+    protected void close(int i) {
+        try {
+            if (connectionReaders[i] != null) {
+                connectionReaders[i].close();
+            }
+        } catch (IOException e) {
+        }
+        connectionReaders[i] = null;
+        try {
+            if (connectionWriters[i] != null) {
+                connectionWriters[i].close();
+            }
+        } catch (IOException e) {
+        }
+        connectionWriters[i] = null;
+        try {
+            if (connections[i] != null) {
+                connections[i].close();
+            }
+        } catch (IOException e) {
+        }
+        connections[i] = null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/context/ReplicatedContext.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/context/ReplicatedContext.java
new file mode 100644
index 0000000..e0df86b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/context/ReplicatedContext.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.context;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletContext;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Loader;
+import org.apache.catalina.core.ApplicationContext;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.tipis.AbstractReplicatedMap.MapOwner;
+import org.apache.catalina.tribes.tipis.ReplicatedMap;
+import org.apache.catalina.util.Enumerator;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class ReplicatedContext extends StandardContext implements MapOwner {
+    private int mapSendOptions = Channel.SEND_OPTIONS_DEFAULT;
+    private static final Log log = LogFactory.getLog( ReplicatedContext.class );
+    protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
+    
+    /**
+     * Start this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void startInternal() throws LifecycleException {
+
+        try {
+            CatalinaCluster catclust = (CatalinaCluster)this.getCluster();
+            if (this.context == null) this.context = new ReplApplContext(this);
+            if ( catclust != null ) {
+                ReplicatedMap map = new ReplicatedMap(this,catclust.getChannel(),DEFAULT_REPL_TIMEOUT,
+                                                      getName(),getClassLoaders());
+                map.setChannelSendOptions(mapSendOptions);
+                ((ReplApplContext)this.context).setAttributeMap(map);
+                if (getAltDDName() != null) context.setAttribute(Globals.ALT_DD_ATTR, getAltDDName());
+            }
+            super.startInternal();
+        }  catch ( Exception x ) {
+            log.error("Unable to start ReplicatedContext",x);
+            throw new LifecycleException("Failed to start ReplicatedContext",x);
+        }
+    }
+    
+    /**
+     * Stop this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void stopInternal() throws LifecycleException {
+        
+        super.stopInternal();
+
+        AbstractMap<String,Object> map =
+            ((ReplApplContext)this.context).getAttributeMap();
+        if ( map!=null && map instanceof ReplicatedMap) {
+            ((ReplicatedMap)map).breakdown();
+        }
+    }
+
+
+    public void setMapSendOptions(int mapSendOptions) {
+        this.mapSendOptions = mapSendOptions;
+    }
+
+    public int getMapSendOptions() {
+        return mapSendOptions;
+    }
+    
+    public ClassLoader[] getClassLoaders() {
+        Loader loader = null;
+        ClassLoader classLoader = null;
+        loader = this.getLoader();
+        if (loader != null) classLoader = loader.getClassLoader();
+        if ( classLoader == null ) classLoader = Thread.currentThread().getContextClassLoader();
+        if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+            return new ClassLoader[] {classLoader};
+        } else {
+            return new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()};
+        }
+    }
+    
+    @Override
+    public ServletContext getServletContext() {
+        if (context == null) {
+            context = new ReplApplContext(this);
+            if (getAltDDName() != null)
+                context.setAttribute(Globals.ALT_DD_ATTR,getAltDDName());
+        }
+
+        return ((ReplApplContext)context).getFacade();
+
+    }
+
+    
+    protected static class ReplApplContext extends ApplicationContext {
+        protected ConcurrentHashMap<String, Object> tomcatAttributes =
+            new ConcurrentHashMap<String, Object>();
+        
+        public ReplApplContext(ReplicatedContext context) {
+            super(context);
+        }
+        
+        protected ReplicatedContext getParent() {
+            return (ReplicatedContext)getContext();
+        }
+        
+        @Override
+        protected ServletContext getFacade() {
+             return super.getFacade();
+        }
+        
+        public AbstractMap<String,Object> getAttributeMap() {
+            return (AbstractMap<String,Object>)this.attributes;
+        }
+        public void setAttributeMap(AbstractMap<String,Object> map) {
+            this.attributes = map;
+        }
+        
+        @Override
+        public void removeAttribute(String name) {
+            tomcatAttributes.remove(name);
+            //do nothing
+            super.removeAttribute(name);
+        }
+        
+        @Override
+        public void setAttribute(String name, Object value) {
+            if ( (!getParent().getState().isAvailable()) || "org.apache.jasper.runtime.JspApplicationContextImpl".equals(name) ){
+                tomcatAttributes.put(name,value);
+            } else
+                super.setAttribute(name,value);
+        }
+        
+        @Override
+        public Object getAttribute(String name) {
+            if (tomcatAttributes.containsKey(name) )
+                return tomcatAttributes.get(name);
+            else 
+                return super.getAttribute(name);
+        }
+        
+        @Override
+        public Enumeration<String> getAttributeNames() {
+            return new MultiEnumeration<String>(new Enumeration[] {super.getAttributeNames(),new Enumerator<String>(tomcatAttributes.keySet(), true)});
+        }
+        
+    }
+
+    protected static class MultiEnumeration<T> implements Enumeration<T> {
+        Enumeration<T>[] e=null;
+        public MultiEnumeration(Enumeration<T>[] lists) {
+            e = lists;
+        }
+        @Override
+        public boolean hasMoreElements() {
+            for ( int i=0; i<e.length; i++ ) {
+                if ( e[i].hasMoreElements() ) return true;
+            }
+            return false;
+        }
+        @Override
+        public T nextElement() {
+            for ( int i=0; i<e.length; i++ ) {
+                if ( e[i].hasMoreElements() ) return e[i].nextElement();
+            }
+            return null;
+
+        }
+    }
+    
+    @Override
+    public void objectMadePrimay(Object key, Object value) {
+        //noop
+    }
+
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/Constants.java
new file mode 100644
index 0000000..084c207
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.deploy;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha.deploy</code>
+ * package.
+ */
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.ha.deploy";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FarmWarDeployer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FarmWarDeployer.java
new file mode 100644
index 0000000..bb903d5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FarmWarDeployer.java
@@ -0,0 +1,741 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.ha.ClusterDeployer;
+import org.apache.catalina.ha.ClusterListener;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.util.ContextName;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * <p>
+ * A farm war deployer is a class that is able to deploy/undeploy web
+ * applications in WAR from within the cluster.
+ * </p>
+ * Any host can act as the admin, and will have three directories
+ * <ul>
+ * <li>deployDir - the directory where we watch for changes</li>
+ * <li>applicationDir - the directory where we install applications</li>
+ * <li>tempDir - a temporaryDirectory to store binary data when downloading a
+ * war from the cluster</li>
+ * </ul>
+ * Currently we only support deployment of WAR files since they are easier to
+ * send across the wire.
+ * 
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Revision: 1.1 $
+ */
+public class FarmWarDeployer extends ClusterListener
+        implements ClusterDeployer, FileChangeListener {
+    /*--Static Variables----------------------------------------*/
+    private static final Log log = LogFactory.getLog(FarmWarDeployer.class);
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "FarmWarDeployer/1.2";
+
+    /*--Instance Variables--------------------------------------*/
+    protected boolean started = false; //default 5 seconds
+
+    protected HashMap<String, FileMessageFactory> fileFactories =
+        new HashMap<String, FileMessageFactory>();
+
+    protected String deployDir;
+
+    protected String tempDir;
+
+    protected String watchDir;
+
+    protected boolean watchEnabled = false;
+
+    protected WarWatcher watcher = null;
+
+    /**
+     * Iteration count for background processing.
+     */
+    private int count = 0;
+
+    /**
+     * Frequency of the Farm watchDir check. Cluster wide deployment will be
+     * done once for the specified amount of backgrondProcess calls (ie, the
+     * lower the amount, the most often the checks will occur).
+     */
+    protected int processDeployFrequency = 2;
+
+    /**
+     * Path where context descriptors should be deployed.
+     */
+    protected File configBase = null;
+
+    /**
+     * The associated host.
+     */
+    protected Host host = null;
+
+    /**
+     * The host appBase.
+     */
+    protected File appBase = null;
+
+    /**
+     * MBean server.
+     */
+    protected MBeanServer mBeanServer = null;
+
+    /**
+     * The associated deployer ObjectName.
+     */
+    protected ObjectName oname = null;
+
+    /*--Constructor---------------------------------------------*/
+    public FarmWarDeployer() {
+    }
+
+    /**
+     * Return descriptive information about this deployer implementation and the
+     * corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    /*--Logic---------------------------------------------------*/
+    @Override
+    public void start() throws Exception {
+        if (started)
+            return;
+        Container hcontainer = getCluster().getContainer();
+        if(!(hcontainer instanceof Host)) {
+            log.error(sm.getString("farmWarDeployer.hostOnly"));
+            return ;
+        }
+        host = (Host) hcontainer;
+    
+        // Check to correct engine and host setup
+        Container econtainer = host.getParent();
+        if(!(econtainer instanceof Engine)) {
+            log.error(sm.getString("farmWarDeployer.hostParentEngine",
+                    host.getName())); 
+            return ;
+        }
+        Engine engine = (Engine) econtainer;
+        String hostname = null;
+        hostname = host.getName();
+        try {
+            oname = new ObjectName(engine.getName() + ":type=Deployer,host="
+                    + hostname);
+        } catch (Exception e) {
+            log.error(sm.getString("farmWarDeployer.mbeanNameFail",
+                    engine.getName(), hostname),e);
+            return;
+        }
+        if (watchEnabled) {
+            watcher = new WarWatcher(this, new File(getWatchDir()));
+            if (log.isInfoEnabled()) {
+                log.info(sm.getString(
+                        "farmWarDeployer.watchDir", getWatchDir()));
+            }
+        }
+         
+        configBase = new File(
+                System.getProperty(Globals.CATALINA_BASE_PROP), "conf");
+        configBase = new File(configBase, engine.getName());
+        configBase = new File(configBase, hostname);
+
+        // Retrieve the MBean server
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+
+        started = true;
+        count = 0;
+
+        getCluster().addClusterListener(this);
+
+        if (log.isInfoEnabled())
+            log.info(sm.getString("farmWarDeployer.started"));
+    }
+
+    /*
+     * stop cluster wide deployments
+     * 
+     * @see org.apache.catalina.ha.ClusterDeployer#stop()
+     */
+    @Override
+    public void stop() throws LifecycleException {
+        started = false;
+        getCluster().removeClusterListener(this);
+        count = 0;
+        if (watcher != null) {
+            watcher.clear();
+            watcher = null;
+
+        }
+        if (log.isInfoEnabled())
+            log.info(sm.getString("farmWarDeployer.stopped"));
+    }
+
+    public void cleanDeployDir() {
+        throw new java.lang.UnsupportedOperationException(sm.getString(
+                "farmWarDeployer.notImplemented", "cleanDeployDir()"));
+    }
+
+    /**
+     * Callback from the cluster, when a message is received, The cluster will
+     * broadcast it invoking the messageReceived on the receiver.
+     * 
+     * @param msg
+     *            ClusterMessage - the message received from the cluster
+     */
+    @Override
+    public void messageReceived(ClusterMessage msg) {
+        try {
+            if (msg instanceof FileMessage) {
+                FileMessage fmsg = (FileMessage) msg;
+                if (log.isDebugEnabled())
+                    log.debug(sm.getString("farmWarDeployer.msgRxDeploy",
+                            fmsg.getContextName(), fmsg.getFileName()));
+                FileMessageFactory factory = getFactory(fmsg);
+                // TODO correct second try after app is in service!
+                if (factory.writeMessage(fmsg)) {
+                    //last message received war file is completed
+                    String name = factory.getFile().getName();
+                    if (!name.endsWith(".war"))
+                        name = name + ".war";
+                    File deployable = new File(getDeployDir(), name);
+                    try {
+                        String contextName = fmsg.getContextName();
+                        if (!isServiced(contextName)) {
+                            addServiced(contextName);
+                            try {
+                                remove(contextName);
+                                if (!factory.getFile().renameTo(deployable)) {
+                                    log.error(sm.getString(
+                                            "farmWarDeployer.renameFail",
+                                            factory.getFile(), deployable));
+                                }
+                                check(contextName);
+                            } finally {
+                                removeServiced(contextName);
+                            }
+                            if (log.isDebugEnabled())
+                                log.debug(sm.getString(
+                                        "farmWarDeployer.deployEnd",
+                                        contextName));
+                        } else
+                            log.error(sm.getString(
+                                    "farmWarDeployer.servicingDeploy",
+                                    contextName, name));
+                    } catch (Exception ex) {
+                        log.error(ex);
+                    } finally {
+                        removeFactory(fmsg);
+                    }
+                }
+            } else if (msg instanceof UndeployMessage) {
+                try {
+                    UndeployMessage umsg = (UndeployMessage) msg;
+                    String contextName = umsg.getContextName();
+                    if (log.isDebugEnabled())
+                        log.debug(sm.getString("farmWarDeployer.msgRxUndeploy",
+                                contextName));
+                    if (!isServiced(contextName)) {
+                        addServiced(contextName);
+                        try {
+                            remove(contextName);
+                        } finally {
+                            removeServiced(contextName);
+                        }
+                        if (log.isDebugEnabled())
+                            log.debug(sm.getString(
+                                    "farmWarDeployer.undeployEnd",
+                                    contextName));
+                    } else
+                        log.error(sm.getString(
+                                "farmWarDeployer.servicingUneploy",
+                                contextName));
+                } catch (Exception ex) {
+                    log.error(ex);
+                }
+            }
+        } catch (java.io.IOException x) {
+            log.error(sm.getString("farmWarDeployer.msgIoe"), x);
+        }
+    }
+
+    /**
+     * create factory for all transported war files
+     * 
+     * @param msg
+     * @return Factory for all app message (war files)
+     * @throws java.io.FileNotFoundException
+     * @throws java.io.IOException
+     */
+    public synchronized FileMessageFactory getFactory(FileMessage msg)
+            throws java.io.FileNotFoundException, java.io.IOException {
+        File writeToFile = new File(getTempDir(), msg.getFileName());
+        FileMessageFactory factory = fileFactories.get(msg.getFileName());
+        if (factory == null) {
+            factory = FileMessageFactory.getInstance(writeToFile, true);
+            fileFactories.put(msg.getFileName(), factory);
+        }
+        return factory;
+    }
+
+    /**
+     * Remove file (war) from messages)
+     * 
+     * @param msg
+     */
+    public void removeFactory(FileMessage msg) {
+        fileFactories.remove(msg.getFileName());
+    }
+
+    /**
+     * Before the cluster invokes messageReceived the cluster will ask the
+     * receiver to accept or decline the message, In the future, when messages
+     * get big, the accept method will only take a message header
+     * 
+     * @param msg
+     *            ClusterMessage
+     * @return boolean - returns true to indicate that messageReceived should be
+     *         invoked. If false is returned, the messageReceived method will
+     *         not be invoked.
+     */
+    @Override
+    public boolean accept(ClusterMessage msg) {
+        return (msg instanceof FileMessage) || (msg instanceof UndeployMessage);
+    }
+
+    /**
+     * Install a new web application, whose web application archive is at the
+     * specified URL, into this container and all the other members of the
+     * cluster with the specified context name.
+     * <p>
+     * If this application is successfully installed locally, a ContainerEvent
+     * of type <code>INSTALL_EVENT</code> will be sent to all registered
+     * listeners, with the newly created <code>Context</code> as an argument.
+     * 
+     * @param contextName
+     *            The context name to which this application should be installed
+     *            (must be unique)
+     * @param webapp
+     *            A WAR file or unpacked directory structure containing the web
+     *            application to be installed
+     * 
+     * @exception IllegalArgumentException
+     *                if the specified context name is malformed
+     * @exception IllegalStateException
+     *                if the specified context name is already deployed
+     * @exception IOException
+     *                if an input/output error was encountered during
+     *                installation
+     */
+    @Override
+    public void install(String contextName, File webapp) throws IOException {
+        Member[] members = getCluster().getMembers();
+        Member localMember = getCluster().getLocalMember();
+        FileMessageFactory factory =
+            FileMessageFactory.getInstance(webapp, false);
+        FileMessage msg = new FileMessage(localMember, webapp.getName(),
+                contextName);
+        if(log.isDebugEnabled())
+            log.debug(sm.getString("farmWarDeployer.sendStart", contextName,
+                    webapp));
+        msg = factory.readMessage(msg);
+        while (msg != null) {
+            for (int i = 0; i < members.length; i++) {
+                if (log.isDebugEnabled())
+                    log.debug(sm.getString("farmWarDeployer.sendFragment",
+                            contextName, webapp, members[i]));
+                getCluster().send(msg, members[i]);
+            }
+            msg = factory.readMessage(msg);
+        }
+        if(log.isDebugEnabled())
+            log.debug(sm.getString(
+                    "farmWarDeployer.sendEnd", contextName, webapp));
+    }
+
+    /**
+     * Remove an existing web application, attached to the specified context
+     * name. If this application is successfully removed, a ContainerEvent of
+     * type <code>REMOVE_EVENT</code> will be sent to all registered
+     * listeners, with the removed <code>Context</code> as an argument.
+     * Deletes the web application war file and/or directory if they exist in
+     * the Host's appBase.
+     * 
+     * @param contextName
+     *            The context name of the application to be removed
+     * @param undeploy
+     *            boolean flag to remove web application from server
+     * 
+     * @exception IllegalArgumentException
+     *                if the specified context name is malformed
+     * @exception IllegalArgumentException
+     *                if the specified context name does not identify a
+     *                currently installed web application
+     * @exception IOException
+     *                if an input/output error occurs during removal
+     */
+    @Override
+    public void remove(String contextName, boolean undeploy)
+            throws IOException {
+        if (log.isInfoEnabled())
+            log.info(sm.getString("farmWarDeployer.removeStart", contextName));
+        Member localMember = getCluster().getLocalMember();
+        UndeployMessage msg = new UndeployMessage(localMember, System
+                .currentTimeMillis(), "Undeploy:" + contextName + ":"
+                + System.currentTimeMillis(), contextName, undeploy);
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("farmWarDeployer.removeTxMsg", contextName));
+        cluster.send(msg);
+        // remove locally
+        if (undeploy) {
+            try {
+                if (!isServiced(contextName)) {
+                    addServiced(contextName);
+                    try {
+                        remove(contextName);
+                    } finally {
+                        removeServiced(contextName);
+                    }
+                } else
+                    log.error(sm.getString("farmWarDeployer.removeFailRemote",
+                            contextName));
+
+            } catch (Exception ex) {
+                log.error(sm.getString("farmWarDeployer.removeFailLocal",
+                        contextName), ex);
+            }
+        }
+
+    }
+
+    /*
+     * Modification from watchDir war detected!
+     * 
+     * @see org.apache.catalina.ha.deploy.FileChangeListener#fileModified(File)
+     */
+    @Override
+    public void fileModified(File newWar) {
+        try {
+            File deployWar = new File(getDeployDir(), newWar.getName());
+            copy(newWar, deployWar);
+            ContextName cn = new ContextName(deployWar.getName());
+            if (log.isInfoEnabled())
+                log.info(sm.getString("farmWarDeployer.modInstall",
+                        cn.getName(), deployWar.getAbsolutePath()));
+            try {
+                remove(cn.getName(), false);
+            } catch (Exception x) {
+                log.error(sm.getString("farmWarDeployer.modRemoveFail"), x);
+            }
+            install(cn.getName(), deployWar);
+        } catch (Exception x) {
+            log.error(sm.getString("farmWarDeployer.modInstallFail"), x);
+        }
+    }
+
+    /*
+     * War remove from watchDir
+     * 
+     * @see org.apache.catalina.ha.deploy.FileChangeListener#fileRemoved(File)
+     */
+    @Override
+    public void fileRemoved(File removeWar) {
+        try {
+            ContextName cn = new ContextName(removeWar.getName());
+            if (log.isInfoEnabled())
+                log.info(sm.getString("farmWarDeployer.removeLocal",
+                        cn.getName()));
+            remove(cn.getName(), true);
+        } catch (Exception x) {
+            log.error(sm.getString("farmWarDeployer.removeLocalFail"), x);
+        }
+    }
+
+    /**
+     * Return a File object representing the "application root" directory for
+     * our associated Host.
+     */
+    protected File getAppBase() {
+
+        if (appBase != null) {
+            return appBase;
+        }
+
+        File file = new File(host.getAppBase());
+        if (!file.isAbsolute())
+            file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), host
+                    .getAppBase());
+        try {
+            appBase = file.getCanonicalFile();
+        } catch (IOException e) {
+            appBase = file;
+        }
+        return (appBase);
+
+    }
+
+    /**
+     * Invoke the remove method on the deployer.
+     */
+    protected void remove(String contextName) throws Exception {
+        // TODO Handle remove also work dir content !
+        // Stop the context first to be nicer
+        Context context = (Context) host.findChild(contextName);
+        if (context != null) {
+            if(log.isDebugEnabled())
+                log.debug(sm.getString("farmWarDeployer.undeployLocal",
+                        contextName));
+            context.stop();
+            String baseName = context.getBaseName();
+            File war = new File(getAppBase(), baseName + ".war");
+            File dir = new File(getAppBase(), baseName);
+            File xml = new File(configBase, baseName + ".xml");
+            if (war.exists()) {
+                if (!war.delete()) {
+                    log.error(sm.getString("farmWarDeployer.deleteFail", war));
+                }
+            } else if (dir.exists()) {
+                undeployDir(dir);
+            } else {
+                if (!xml.delete()) {
+                    log.error(sm.getString("farmWarDeployer.deleteFail", xml));
+                }
+            }
+            // Perform new deployment and remove internal HostConfig state
+            check(contextName);
+        }
+
+    }
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * subdirectories recursively.
+     * 
+     * @param dir
+     *            File object representing the directory to be deleted
+     */
+    protected void undeployDir(File dir) {
+
+        String files[] = dir.list();
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; i < files.length; i++) {
+            File file = new File(dir, files[i]);
+            if (file.isDirectory()) {
+                undeployDir(file);
+            } else {
+                if (!file.delete()) {
+                    log.error(sm.getString("farmWarDeployer.deleteFail", file));
+                }
+            }
+        }
+        if (!dir.delete()) {
+            log.error(sm.getString("farmWarDeployer.deleteFail", dir));
+        }
+    }
+
+    /*
+     * Call watcher to check for deploy changes
+     * 
+     * @see org.apache.catalina.ha.ClusterDeployer#backgroundProcess()
+     */
+    @Override
+    public void backgroundProcess() {
+        if (started) {
+            count = (count + 1) % processDeployFrequency;
+            if (count == 0 && watchEnabled) {
+                watcher.check();
+            }
+        }
+
+    }
+
+    /*--Deployer Operations ------------------------------------*/
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected void check(String name) throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "check", params, signature);
+    }
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected boolean isServiced(String name) throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        Boolean result = (Boolean) mBeanServer.invoke(oname, "isServiced",
+                params, signature);
+        return result.booleanValue();
+    }
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected void addServiced(String name) throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "addServiced", params, signature);
+    }
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected void removeServiced(String name) throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "removeServiced", params, signature);
+    }
+
+    /*--Instance Getters/Setters--------------------------------*/
+    @Override
+    public boolean equals(Object listener) {
+        return super.equals(listener);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    public String getDeployDir() {
+        return deployDir;
+    }
+
+    public void setDeployDir(String deployDir) {
+        this.deployDir = deployDir;
+    }
+
+    public String getTempDir() {
+        return tempDir;
+    }
+
+    public void setTempDir(String tempDir) {
+        this.tempDir = tempDir;
+    }
+
+    public String getWatchDir() {
+        return watchDir;
+    }
+
+    public void setWatchDir(String watchDir) {
+        this.watchDir = watchDir;
+    }
+
+    public boolean isWatchEnabled() {
+        return watchEnabled;
+    }
+
+    public boolean getWatchEnabled() {
+        return watchEnabled;
+    }
+
+    public void setWatchEnabled(boolean watchEnabled) {
+        this.watchEnabled = watchEnabled;
+    }
+
+    /**
+     * Return the frequency of watcher checks.
+     */
+    public int getProcessDeployFrequency() {
+
+        return (this.processDeployFrequency);
+
+    }
+
+    /**
+     * Set the watcher checks frequency.
+     * 
+     * @param processExpiresFrequency
+     *            the new manager checks frequency
+     */
+    public void setProcessDeployFrequency(int processExpiresFrequency) {
+
+        if (processExpiresFrequency <= 0) {
+            return;
+        }
+        this.processDeployFrequency = processExpiresFrequency;
+    }
+
+    /**
+     * Copy a file to the specified temp directory.
+     * @param from copy from temp
+     * @param to   to host appBase directory
+     * @return true, copy successful
+     */
+    protected boolean copy(File from, File to) {
+        try {
+            if (!to.exists()) {
+                if (!to.createNewFile()) {
+                    log.error(sm.getString("fileNewFail", to));
+                    return false;
+                }
+            }
+            java.io.FileInputStream is = new java.io.FileInputStream(from);
+            java.io.FileOutputStream os = new java.io.FileOutputStream(to,
+                    false);
+            byte[] buf = new byte[4096];
+            while (true) {
+                int len = is.read(buf);
+                if (len < 0)
+                    break;
+                os.write(buf, 0, len);
+            }
+            is.close();
+            os.close();
+        } catch (IOException e) {
+            log.error(sm.getString("farmWarDeployer.fileCopyFail",
+                    from, to), e);
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileChangeListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileChangeListener.java
new file mode 100644
index 0000000..4358cac
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileChangeListener.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+import java.io.File;
+
+public interface FileChangeListener {
+    public void fileModified(File f);
+    public void fileRemoved(File f);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileMessage.java
new file mode 100644
index 0000000..f030347
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileMessage.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import org.apache.catalina.ha.ClusterMessageBase;
+import org.apache.catalina.tribes.Member;
+
+/**
+ * Contains the data for a file being transferred over TCP, this is 
+ * essentially a fragment of a file, read and written by the FileMessageFactory
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public class FileMessage extends ClusterMessageBase {
+    private static final long serialVersionUID = 1L;
+
+    private int messageNumber;
+    private byte[] data;
+    private int dataLength;
+    
+    private long totalLength;
+    private long totalNrOfMsgs;
+    private String fileName;
+    private String contextName;
+    
+    public FileMessage(Member source,
+                       String fileName,
+                       String contextName) {
+        this.address=source;
+        this.fileName=fileName;
+        this.contextName=contextName;
+    }
+    
+    /*
+    public void writeExternal(ObjectOutput out) throws IOException {
+                   
+    }
+    
+    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+                  
+    }
+    */
+   
+    public int getMessageNumber() {
+        return messageNumber;
+    }
+    public void setMessageNumber(int messageNumber) {
+        this.messageNumber = messageNumber;
+    }
+    public long getTotalNrOfMsgs() {
+        return totalNrOfMsgs;
+    }
+    public void setTotalNrOfMsgs(long totalNrOfMsgs) {
+        this.totalNrOfMsgs = totalNrOfMsgs;
+    }
+    public byte[] getData() {
+        return data;
+    }
+    public void setData(byte[] data, int length) {
+        this.data = data;
+        this.dataLength = length;
+    }
+    public int getDataLength() {
+        return dataLength;
+    }
+    public void setDataLength(int dataLength) {
+        this.dataLength = dataLength;
+    }
+    public long getTotalLength() {
+        return totalLength;
+    }
+    public void setTotalLength(long totalLength) {
+        this.totalLength = totalLength;
+    }
+
+    @Override
+    public String getUniqueId() {
+        StringBuilder result = new StringBuilder(getFileName());
+        result.append("#-#");
+        result.append(getMessageNumber());
+        result.append("#-#");
+        result.append(System.currentTimeMillis());
+        return result.toString();
+    }
+
+    
+    public String getFileName() {
+        return fileName;
+    }
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+    public String getContextName() {
+        return contextName;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileMessageFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileMessageFactory.java
new file mode 100644
index 0000000..673a0af
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/FileMessageFactory.java
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * This factory is used to read files and write files by splitting them up into
+ * smaller messages. So that entire files don't have to be read into memory.
+ * <BR>
+ * The factory can be used as a reader or writer but not both at the same time.
+ * When done reading or writing the factory will close the input or output
+ * streams and mark the factory as closed. It is not possible to use it after
+ * that. <BR>
+ * To force a cleanup, call cleanup() from the calling object. <BR>
+ * This class is not thread safe.
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class FileMessageFactory {
+    /*--Static Variables----------------------------------------*/
+    private static final Log log = LogFactory.getLog(FileMessageFactory.class);
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * The number of bytes that we read from file
+     */
+    public static final int READ_SIZE = 1024 * 10; //10kb
+
+    /**
+     * The file that we are reading/writing
+     */
+    protected File file = null;
+
+    /**
+     * True means that we are writing with this factory. False means that we are
+     * reading with this factory
+     */
+    protected boolean openForWrite;
+
+    /**
+     * Once the factory is used, it can not be reused.
+     */
+    protected boolean closed = false;
+
+    /**
+     * When openForWrite=false, the input stream is held by this variable
+     */
+    protected FileInputStream in;
+
+    /**
+     * When openForWrite=true, the output stream is held by this variable
+     */
+    protected FileOutputStream out;
+
+    /**
+     * The number of messages we have written
+     */
+    protected int nrOfMessagesProcessed = 0;
+
+    /**
+     * The total size of the file
+     */
+    protected long size = 0;
+
+    /**
+     * The total number of packets that we split this file into
+     */
+    protected long totalNrOfMessages = 0;
+    
+    /**
+     * The number of the last message processed. Message IDs are 1 based.
+     */
+    protected AtomicLong lastMessageProcessed = new AtomicLong(0);
+    
+    /**
+     * Messages received out of order are held in the buffer until required. If
+     * everything is worked as expected, messages will spend very little time in
+     * the buffer.
+     */
+    protected Map<Long, FileMessage> msgBuffer =
+        new ConcurrentHashMap<Long, FileMessage>();
+
+    /**
+     * The bytes that we hold the data in, not thread safe.
+     */
+    protected byte[] data = new byte[READ_SIZE];
+
+    /**
+     * Flag that indicates if a thread is writing messages to disk. Access to
+     * this flag must be synchronised.
+     */
+    protected boolean isWriting = false;
+
+    /**
+     * Private constructor, either instantiates a factory to read or write. <BR>
+     * When openForWrite==true, then a the file, f, will be created and an
+     * output stream is opened to write to it. <BR>
+     * When openForWrite==false, an input stream is opened, the file has to
+     * exist.
+     * 
+     * @param f
+     *            File - the file to be read/written
+     * @param openForWrite
+     *            boolean - true means we are writing to the file, false means
+     *            we are reading from the file
+     * @throws FileNotFoundException -
+     *             if the file to be read doesn't exist
+     * @throws IOException -
+     *             if the system fails to open input/output streams to the file
+     *             or if it fails to create the file to be written to.
+     */
+    private FileMessageFactory(File f, boolean openForWrite)
+            throws FileNotFoundException, IOException {
+        this.file = f;
+        this.openForWrite = openForWrite;
+        if (log.isDebugEnabled())
+            log.debug("open file " + f + " write " + openForWrite);
+        if (openForWrite) {
+            if (!file.exists())
+                if (!file.createNewFile()) {
+                    throw new IOException(sm.getString("fileNewFail", file));
+                }
+            out = new FileOutputStream(f);
+        } else {
+            size = file.length();
+            totalNrOfMessages = (size / READ_SIZE) + 1;
+            in = new FileInputStream(f);
+        }//end if
+
+    }
+
+    /**
+     * Creates a factory to read or write from a file. When opening for read,
+     * the readMessage can be invoked, and when opening for write the
+     * writeMessage can be invoked.
+     * 
+     * @param f
+     *            File - the file to be read or written
+     * @param openForWrite
+     *            boolean - true, means we are writing to the file, false means
+     *            we are reading from it
+     * @throws FileNotFoundException -
+     *             if the file to be read doesn't exist
+     * @throws IOException -
+     *             if it fails to create the file that is to be written
+     * @return FileMessageFactory
+     */
+    public static FileMessageFactory getInstance(File f, boolean openForWrite)
+            throws FileNotFoundException, IOException {
+        return new FileMessageFactory(f, openForWrite);
+    }
+
+    /**
+     * Reads file data into the file message and sets the size, totalLength,
+     * totalNrOfMsgs and the message number <BR>
+     * If EOF is reached, the factory returns null, and closes itself, otherwise
+     * the same message is returned as was passed in. This makes sure that not
+     * more memory is ever used. To remember, neither the file message or the
+     * factory are thread safe. dont hand off the message to one thread and read
+     * the same with another.
+     * 
+     * @param f
+     *            FileMessage - the message to be populated with file data
+     * @throws IllegalArgumentException -
+     *             if the factory is for writing or is closed
+     * @throws IOException -
+     *             if a file read exception occurs
+     * @return FileMessage - returns the same message passed in as a parameter,
+     *         or null if EOF
+     */
+    public FileMessage readMessage(FileMessage f)
+            throws IllegalArgumentException, IOException {
+        checkState(false);
+        int length = in.read(data);
+        if (length == -1) {
+            cleanup();
+            return null;
+        } else {
+            f.setData(data, length);
+            f.setTotalLength(size);
+            f.setTotalNrOfMsgs(totalNrOfMessages);
+            f.setMessageNumber(++nrOfMessagesProcessed);
+            return f;
+        }//end if
+    }
+
+    /**
+     * Writes a message to file. If (msg.getMessageNumber() ==
+     * msg.getTotalNrOfMsgs()) the output stream will be closed after writing.
+     * 
+     * @param msg
+     *            FileMessage - message containing data to be written
+     * @throws IllegalArgumentException -
+     *             if the factory is opened for read or closed
+     * @throws IOException -
+     *             if a file write error occurs
+     * @return returns true if the file is complete and outputstream is closed,
+     *         false otherwise.
+     */
+    public boolean writeMessage(FileMessage msg)
+            throws IllegalArgumentException, IOException {
+        if (!openForWrite)
+            throw new IllegalArgumentException(
+                    "Can't write message, this factory is reading.");
+        if (log.isDebugEnabled())
+            log.debug("Message " + msg + " data " + HexUtils.toHexString(msg.getData())
+                    + " data length " + msg.getDataLength() + " out " + out);
+        
+        if (msg.getMessageNumber() <= lastMessageProcessed.get()) {
+            // Duplicate of message already processed
+            log.warn("Receive Message again -- Sender ActTimeout too short [ name: "
+                    + msg.getContextName()
+                    + " war: "
+                    + msg.getFileName()
+                    + " data: "
+                    + HexUtils.toHexString(msg.getData())
+                    + " data length: " + msg.getDataLength() + " ]");
+            return false;
+        }
+        
+        FileMessage previous =
+            msgBuffer.put(Long.valueOf(msg.getMessageNumber()), msg);
+        if (previous !=null) {
+            // Duplicate of message not yet processed
+            log.warn("Receive Message again -- Sender ActTimeout too short [ name: "
+                    + msg.getContextName()
+                    + " war: "
+                    + msg.getFileName()
+                    + " data: "
+                    + HexUtils.toHexString(msg.getData())
+                    + " data length: " + msg.getDataLength() + " ]");
+            return false;
+        }
+        
+        FileMessage next = null;
+        synchronized (this) {
+            if (!isWriting) {
+                next = msgBuffer.get(Long.valueOf(lastMessageProcessed.get() + 1));
+                if (next != null) {
+                    isWriting = true;
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+        
+        while (next != null) {
+            out.write(next.getData(), 0, next.getDataLength());
+            lastMessageProcessed.incrementAndGet();
+            out.flush();
+            if (next.getMessageNumber() == next.getTotalNrOfMsgs()) {
+                out.close();
+                cleanup();
+                return true;
+            }
+            synchronized(this) {
+                next =
+                    msgBuffer.get(Long.valueOf(lastMessageProcessed.get() + 1));
+                if (next == null) {
+                    isWriting = false;
+                }
+            }
+        }
+        
+        return false;
+    }//writeMessage
+
+    /**
+     * Closes the factory, its streams and sets all its references to null
+     */
+    public void cleanup() {
+        if (in != null)
+            try {
+                in.close();
+            } catch (Exception ignore) {
+            }
+        if (out != null)
+            try {
+                out.close();
+            } catch (Exception ignore) {
+            }
+        in = null;
+        out = null;
+        size = 0;
+        closed = true;
+        data = null;
+        nrOfMessagesProcessed = 0;
+        totalNrOfMessages = 0;
+        msgBuffer.clear();
+        lastMessageProcessed = null;
+    }
+
+    /**
+     * Check to make sure the factory is able to perform the function it is
+     * asked to do. Invoked by readMessage/writeMessage before those methods
+     * proceed.
+     * 
+     * @param openForWrite
+     *            boolean
+     * @throws IllegalArgumentException
+     */
+    protected void checkState(boolean openForWrite)
+            throws IllegalArgumentException {
+        if (this.openForWrite != openForWrite) {
+            cleanup();
+            if (openForWrite)
+                throw new IllegalArgumentException(
+                        "Can't write message, this factory is reading.");
+            else
+                throw new IllegalArgumentException(
+                        "Can't read message, this factory is writing.");
+        }
+        if (this.closed) {
+            cleanup();
+            throw new IllegalArgumentException("Factory has been closed.");
+        }
+    }
+
+    /**
+     * Example usage.
+     * 
+     * @param args
+     *            String[], args[0] - read from filename, args[1] write to
+     *            filename
+     * @throws Exception
+     */
+    public static void main(String[] args) throws Exception {
+
+        System.out
+                .println("Usage: FileMessageFactory fileToBeRead fileToBeWritten");
+        System.out
+                .println("Usage: This will make a copy of the file on the local file system");
+        FileMessageFactory read = getInstance(new File(args[0]), false);
+        FileMessageFactory write = getInstance(new File(args[1]), true);
+        FileMessage msg = new FileMessage(null, args[0], args[0]);
+        msg = read.readMessage(msg);
+        System.out.println("Expecting to write " + msg.getTotalNrOfMsgs()
+                + " messages.");
+        int cnt = 0;
+        while (msg != null) {
+            write.writeMessage(msg);
+            cnt++;
+            msg = read.readMessage(msg);
+        }//while
+        System.out.println("Actually wrote " + cnt + " messages.");
+    }///main
+
+    public File getFile() {
+        return file;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/LocalStrings.properties
new file mode 100644
index 0000000..8550c20
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/LocalStrings.properties
@@ -0,0 +1,47 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+fileNewFail=Unable to create [{0}]
+
+farmWarDeployer.deleteFail=Failed to delete [{0}]
+farmWarDeployer.deployEnd=Deployment from [{0}] finished.
+farmWarDeployer.fileCopyFail=Unable to copy from [{0}] to [{1}]
+farmWarDeployer.hostOnly=FarmWarDeployer can only work as host cluster subelement!
+farmWarDeployer.hostParentEngine=FarmWarDeployer can only work if parent of [{0}] is an engine!
+farmWarDeployer.mbeanNameFail=Can't construct MBean object name for engine [{0}] and host [{1}]
+farmWarDeployer.modInstall=Installing webapp [{0}] from [{1}] 
+farmWarDeployer.modRemoveFail=No removal
+farmWarDeployer.modInstallFail=Unable to install WAR file
+farmWarDeployer.msgIoe=Unable to read farm deploy file message.
+farmWarDeployer.msgRxDeploy=Receive cluster deployment path [{0}], war [{1}]
+farmWarDeployer.msgRxUndeploy=Receive cluster undeployment from path [{0}]
+farmWarDeployer.notImplemented=Method [{0}] not yet implemented.
+farmWarDeployer.removeStart=Cluster wide remove of web app [{0}]
+farmWarDeployer.removeTxMsg=Send cluster wide undeployment from [{1}]
+farmWarDeployer.removeFailRemote=Local remove from [{0}] failed, other manager has app in service!
+farmWarDeployer.removeFailLocal=Local remove from [{0}] failed
+farmWarDeployer.removeLocal=Removing webapp [{0}]
+farmWarDeployer.removeLocalFail=Unable to remove WAR file
+farmWarDeployer.renameFail=Failed to rename [{0}] to [{1}]
+farmWarDeployer.sendEnd=Send cluster war deployment path [{0}], war [{1}] finished.
+farmWarDeployer.sendFragment=Send cluster war fragment path [{0}], war [{1}] to [{2}]
+farmWarDeployer.sendStart=Send cluster war deployment path [{0}], war [{1}] started.
+farmWarDeployer.servicingDeploy=Application [{0}] is being serviced. Touch war file [{1}] again!
+farmWarDeployer.servicingUneploy=Application [{0}] is being serviced and can't be removed from backup cluster node
+farmWarDeployer.started=Cluster FarmWarDeployer started.
+farmWarDeployer.stopped=Cluster FarmWarDeployer stopped.
+farmWarDeployer.undeployEnd=Undeployment from [{0}] finished.
+farmWarDeployer.undeployLocal=Undeploy local context [{0}]
+farmWarDeployer.watchDir=Cluster deployment is watching [{0}] for changes.
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/UndeployMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/UndeployMessage.java
new file mode 100644
index 0000000..33d6e58
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/UndeployMessage.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Member;
+
+public class UndeployMessage implements ClusterMessage {
+    private static final long serialVersionUID = 1L;
+
+    private Member address;
+    private long timestamp;
+    private String uniqueId;
+    private String contextName;
+    private boolean undeploy;
+    private int resend = 0;
+    private int compress = 0;
+
+    public UndeployMessage() {} //for serialization
+    public UndeployMessage(Member address,
+                           long timestamp,
+                           String uniqueId,
+                           String contextName,
+                           boolean undeploy) {
+        this.address  = address;
+        this.timestamp= timestamp;
+        this.undeploy = undeploy;
+        this.uniqueId = uniqueId;
+        this.undeploy = undeploy;
+        this.contextName = contextName;
+    }
+
+    @Override
+    public Member getAddress() {
+        return address;
+    }
+
+    @Override
+    public void setAddress(Member address) {
+        this.address = address;
+    }
+
+    @Override
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public String getUniqueId() {
+        return uniqueId;
+    }
+
+    @Override
+    public void setUniqueId(String uniqueId) {
+        this.uniqueId = uniqueId;
+    }
+
+    public String getContextName() {
+        return contextName;
+    }
+
+    public void setContextPath(String contextName) {
+        this.contextName = contextName;
+    }
+
+    public boolean getUndeploy() {
+        return undeploy;
+    }
+
+    public void setUndeploy(boolean undeploy) {
+        this.undeploy = undeploy;
+    }
+    /**
+     * @return Returns the compress.
+     * @since 5.5.10 
+     */
+    public int getCompress() {
+        return compress;
+    }
+    /**
+     * @param compress The compress to set.
+     * @since 5.5.10
+     */
+    public void setCompress(int compress) {
+        this.compress = compress;
+    }
+    /**
+     * @return Returns the resend.
+     * @since 5.5.10
+     */
+    public int getResend() {
+        return resend;
+    }
+    /**
+     * @param resend The resend to set.
+     * @since 5.5.10
+     */
+    public void setResend(int resend) {
+        this.resend = resend;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/WarWatcher.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/WarWatcher.java
new file mode 100644
index 0000000..2e7cfaf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/WarWatcher.java
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.deploy;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * <p>
+ * The <b>WarWatcher </b> watches the deployDir for changes made to the
+ * directory (adding new WAR files->deploy or remove WAR files->undeploy) And
+ * notifies a listener of the changes made
+ * </p>
+ * 
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version 1.1
+ */
+
+public class WarWatcher {
+
+    /*--Static Variables----------------------------------------*/
+    private static final Log log = LogFactory.getLog(WarWatcher.class);
+
+    /*--Instance Variables--------------------------------------*/
+    /**
+     * Directory to watch for war files
+     */
+    protected File watchDir = null;
+
+    /**
+     * Parent to be notified of changes
+     */
+    protected FileChangeListener listener = null;
+
+    /**
+     * Currently deployed files
+     */
+    protected Map<String, WarInfo> currentStatus =
+        new HashMap<String, WarInfo>();
+
+    /*--Constructor---------------------------------------------*/
+
+    public WarWatcher() {
+    }
+
+    public WarWatcher(FileChangeListener listener, File watchDir) {
+        this.listener = listener;
+        this.watchDir = watchDir;
+    }
+
+    /*--Logic---------------------------------------------------*/
+
+    /**
+     * check for modification and send notification to listener
+     */
+    public void check() {
+        if (log.isDebugEnabled())
+            log.debug("check cluster wars at " + watchDir);
+        File[] list = watchDir.listFiles(new WarFilter());
+        if (list == null)
+            list = new File[0];
+        //first make sure all the files are listed in our current status
+        for (int i = 0; i < list.length; i++) {
+            addWarInfo(list[i]);
+        }
+
+        //check all the status codes and update the FarmDeployer
+        for (Iterator<Map.Entry<String,WarInfo>> i =
+                currentStatus.entrySet().iterator(); i.hasNext();) {
+            Map.Entry<String,WarInfo> entry = i.next();
+            WarInfo info = entry.getValue();
+            int check = info.check();
+            if (check == 1) {
+                listener.fileModified(info.getWar());
+            } else if (check == -1) {
+                listener.fileRemoved(info.getWar());
+                //no need to keep in memory
+                i.remove();
+            }
+        }
+
+    }
+
+    /**
+     * add cluster war to the watcher state
+     * @param warfile
+     */
+    protected void addWarInfo(File warfile) {
+        WarInfo info = currentStatus.get(warfile.getAbsolutePath());
+        if (info == null) {
+            info = new WarInfo(warfile);
+            info.setLastState(-1); //assume file is non existent
+            currentStatus.put(warfile.getAbsolutePath(), info);
+        }
+    }
+
+    /**
+     * clear watcher state
+     */
+    public void clear() {
+        currentStatus.clear();
+    }
+
+    /**
+     * @return Returns the watchDir.
+     */
+    public File getWatchDir() {
+        return watchDir;
+    }
+
+    /**
+     * @param watchDir
+     *            The watchDir to set.
+     */
+    public void setWatchDir(File watchDir) {
+        this.watchDir = watchDir;
+    }
+
+    /**
+     * @return Returns the listener.
+     */
+    public FileChangeListener getListener() {
+        return listener;
+    }
+
+    /**
+     * @param listener
+     *            The listener to set.
+     */
+    public void setListener(FileChangeListener listener) {
+        this.listener = listener;
+    }
+
+    /*--Inner classes-------------------------------------------*/
+
+    /**
+     * File name filter for war files
+     */
+    protected static class WarFilter implements java.io.FilenameFilter {
+        @Override
+        public boolean accept(File path, String name) {
+            if (name == null)
+                return false;
+            return name.endsWith(".war");
+        }
+    }
+
+    /**
+     * File information on existing WAR files
+     */
+    protected static class WarInfo {
+        protected File war = null;
+
+        protected long lastChecked = 0;
+
+        protected long lastState = 0;
+
+        public WarInfo(File war) {
+            this.war = war;
+            this.lastChecked = war.lastModified();
+            if (!war.exists())
+                lastState = -1;
+        }
+
+        public boolean modified() {
+            return war.exists() && war.lastModified() > lastChecked;
+        }
+
+        public boolean exists() {
+            return war.exists();
+        }
+
+        /**
+         * Returns 1 if the file has been added/modified, 0 if the file is
+         * unchanged and -1 if the file has been removed
+         * 
+         * @return int 1=file added; 0=unchanged; -1=file removed
+         */
+        public int check() {
+            //file unchanged by default
+            int result = 0;
+
+            if (modified()) {
+                //file has changed - timestamp
+                result = 1;
+                lastState = result;
+            } else if ((!exists()) && (!(lastState == -1))) {
+                //file was removed
+                result = -1;
+                lastState = result;
+            } else if ((lastState == -1) && exists()) {
+                //file was added
+                result = 1;
+                lastState = result;
+            }
+            this.lastChecked = System.currentTimeMillis();
+            return result;
+        }
+
+        public File getWar() {
+            return war;
+        }
+
+        @Override
+        public int hashCode() {
+            return war.getAbsolutePath().hashCode();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof WarInfo) {
+                WarInfo wo = (WarInfo) other;
+                return wo.getWar().equals(getWar());
+            } else {
+                return false;
+            }
+        }
+
+        protected void setLastState(int lastState) {
+            this.lastState = lastState;
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/mbeans-descriptors.xml
new file mode 100644
index 0000000..76a1746
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/deploy/mbeans-descriptors.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<mbeans-descriptors>
+  <mbean
+    name="FarmWarDeployer"
+    className="org.apache.catalina.mbeans.ClassNameMBean"
+    description="Farm Deployer - Broken"
+    domain="Catalina"
+    group="Cluster"
+    type="org.apache.catalina.ha.deploy.FarmWarDeployer"/>
+    <attribute
+      name="deployDir"
+      description="Deployment directory."
+      type="java.lang.String"/>
+    <attribute
+      name="tempDir"
+      description="The temporaryDirectory to store binary data when downloading a war from the cluster"
+      type="java.lang.String"/>
+    <attribute
+      name="watchDir"
+      description="The directory where we watch for changes"
+      type="java.lang.String"/>
+    <attribute
+      name="watchEnabled"
+      description="Is watching enabled?"
+      type="boolean"/>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/jmx/ClusterJmxHelper.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/jmx/ClusterJmxHelper.java
new file mode 100644
index 0000000..3b0a173
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/jmx/ClusterJmxHelper.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.jmx;
+
+import javax.management.DynamicMBean;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.apache.catalina.core.StandardEngine;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.ha.authenticator.ClusterSingleSignOn;
+import org.apache.catalina.ha.deploy.FarmWarDeployer;
+import org.apache.catalina.ha.session.DeltaManager;
+import org.apache.catalina.ha.tcp.SimpleTcpCluster;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+/**
+ * 
+ * @author Filip Hanik
+ */
+public class ClusterJmxHelper {
+    
+    protected static Registry registry = Registry.getRegistry(null,null);
+    
+    private static final Log log = LogFactory.getLog(ClusterJmxHelper.class);
+    
+    protected static boolean jmxEnabled = true;
+    
+    protected static MBeanServer mbeanServer = null;
+    
+    public static Registry getRegistry() {
+        return registry;
+    }
+
+    public static MBeanServer getMBeanServer() throws Exception {
+        if (mbeanServer == null) {
+            if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
+                mbeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
+            } else {
+                mbeanServer = MBeanServerFactory.createMBeanServer();
+            }
+        }
+        return mbeanServer;
+    }
+    
+    protected static boolean initMetaData(Class<?> clazz) {
+        try {
+            if (clazz==null) return false;
+            getRegistry().loadMetadata(clazz.getResourceAsStream("mbeans-descriptors.xml"));
+        }catch (Exception x) {
+            log.warn("Unable to load meta data for class:"+clazz.getName());
+            return false;
+        }
+        return true;
+    }
+    
+    public static DynamicMBean getManagedBean(Object object) throws Exception {
+        DynamicMBean mbean = null;
+        if (getRegistry() != null) {
+            ManagedBean managedBean = registry.findManagedBean(object.getClass().getName());
+            mbean = managedBean.createMBean(object);
+        }
+        return mbean;
+    }
+
+    
+    protected static void initDefaultCluster() {
+        initMetaData(SimpleTcpCluster.class);
+        initMetaData(DeltaManager.class);
+        initMetaData(FarmWarDeployer.class); //not functional yet
+        initMetaData(ClusterSingleSignOn.class); //not functional yet
+    }
+    
+    public static boolean registerDefaultCluster(SimpleTcpCluster cluster)  {
+        try {
+            initDefaultCluster();
+            ObjectName clusterName = getDefaultClusterName(cluster);
+            if (!getMBeanServer().isRegistered(clusterName)) {
+                getMBeanServer().registerMBean(getManagedBean(cluster), clusterName);
+            }
+            return true;
+        }catch ( Exception x ) {
+            log.warn("Unable to register default cluster implementation with JMX",x);
+            return false;
+        }
+    }
+
+    public static boolean unregisterDefaultCluster(SimpleTcpCluster cluster) {
+        try {
+            ObjectName clusterName = getDefaultClusterName(cluster);
+            if (getMBeanServer().isRegistered(clusterName)) {
+                getMBeanServer().unregisterMBean(clusterName);
+            }
+            return true;
+        }catch ( Exception x ) {
+            log.warn("Unable to unregister default cluster implementation with JMX",x);
+            return false;
+        }
+    }
+
+    private static ObjectName getDefaultClusterName(SimpleTcpCluster cluster) throws Exception {
+        String domain = getMBeanServer().getDefaultDomain();
+        String type = ":type=";
+        String clusterType= type+"Cluster";
+        if (cluster.getContainer() instanceof StandardHost) {
+            domain = ((StandardHost) cluster.getContainer()).getDomain();
+            clusterType += ",host=" + cluster.getContainer().getName();
+        } else {
+            if (cluster.getContainer() instanceof StandardEngine) {
+                domain = ((StandardEngine) cluster.getContainer()).getDomain();
+            }
+        }
+        ObjectName clusterName = new ObjectName(domain + clusterType);
+        return clusterName;
+    }
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/package.html
new file mode 100644
index 0000000..936fb35
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/package.html
@@ -0,0 +1,23 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<body>
+
+<p>This package contains code for Clustering, the base class
+of a Cluster is <code>org.apache.catalina.Cluster</code> implementations
+of this class is done when implementing a new Cluster protocol</p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/util/IDynamicProperty.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/util/IDynamicProperty.java
new file mode 100644
index 0000000..b24a39d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/util/IDynamicProperty.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.util;
+
+import java.util.Iterator;
+
+/**
+ * @author Peter Rossbach
+ * @version $Id: IDynamicProperty.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+
+public interface IDynamicProperty {
+
+    /**
+     * set config attributes with reflect
+     * 
+     * @param name
+     * @param value
+     */
+    public boolean setProperty(String name, Object value) ;
+
+    /**
+     * get current config
+     * 
+     * @param key
+     * @return The property
+     */
+    public Object getProperty(String key) ;
+    /**
+     * Get all properties keys
+     * 
+     * @return An iterator over the property names
+     */
+    public Iterator<String> getPropertyNames() ;
+
+    /**
+     * remove a configured property.
+     * 
+     * @param key
+     */
+    public void removeProperty(String key) ;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/Constants.java
new file mode 100644
index 0000000..56e88aa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/Constants.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.loader";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/JdbcLeakPrevention.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/JdbcLeakPrevention.java
new file mode 100644
index 0000000..48b2668
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/JdbcLeakPrevention.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * This class is loaded by the {@link WebappClassLoader} to enable it to
+ * deregister JDBC drivers forgotten by the web application. There are some
+ * classloading hacks involved - see {@link WebappClassLoader#clearReferences()}
+ * for details - but the short version is do not just create a new instance of
+ * this class with the new keyword.
+ * 
+ * Since this class is loaded by {@link WebappClassLoader}, it can not refer to
+ * any internal Tomcat classes as that will cause the security manager to
+ * complain.
+ */
+public class JdbcLeakPrevention {
+
+    public List<String> clearJdbcDriverRegistrations() throws SQLException {
+        List<String> driverNames = new ArrayList<String>();
+
+        /*
+         * DriverManager.getDrivers() has a nasty side-effect of registering
+         * drivers that are visible to this class loader but haven't yet been
+         * loaded. Therefore, the first call to this method a) gets the list
+         * of originally loaded drivers and b) triggers the unwanted
+         * side-effect. The second call gets the complete list of drivers
+         * ensuring that both original drivers and any loaded as a result of the
+         * side-effects are all de-registered.
+         */
+        HashSet<Driver> originalDrivers = new HashSet<Driver>();
+        Enumeration<Driver> drivers = DriverManager.getDrivers();
+        while (drivers.hasMoreElements()) {
+            originalDrivers.add(drivers.nextElement());
+        }
+        drivers = DriverManager.getDrivers();
+        while (drivers.hasMoreElements()) {
+            Driver driver = drivers.nextElement();
+            // Only unload the drivers this web app loaded
+            if (driver.getClass().getClassLoader() !=
+                this.getClass().getClassLoader()) {
+                continue;
+            }
+            // Only report drivers that were originally registered. Skip any
+            // that were registered as a side-effect of this code.
+            if (originalDrivers.contains(driver)) {
+                driverNames.add(driver.getClass().getCanonicalName());
+            }
+            DriverManager.deregisterDriver(driver);
+        }
+        return driverNames;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings.properties
new file mode 100644
index 0000000..8b23ce1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings.properties
@@ -0,0 +1,78 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+fileClassLoader.canRead=Repository {0} cannot be read
+fileClassLoader.exists=Repository {0} does not exist
+fileClassLoader.jarFile=Cannot read JAR file {0}
+fileClassLoader.restricted=Cannot load restricted class {0}
+standardLoader.addRepository=Adding repository {0}
+standardLoader.alreadyStarted=Loader has already been started
+standardLoader.checkInterval=Cannot set reload check interval to {0} seconds
+standardLoader.notContext=Cannot auto-reload unless our Container is a Context
+standardLoader.notReloadabe=Reloadable property is set to false
+standardLoader.notStarted=Loader has not yet been started
+standardLoader.reloadable=Cannot set reloadable property to {0}
+standardLoader.reloading=Reloading checks are enabled for this Context
+standardLoader.removeRepository=Removing repository {0}
+standardLoader.starting=Starting this Loader
+standardLoader.stopping=Stopping this Loader
+virtualWebappLoader.token=Processing token [{0}]
+virtualWebappLoader.token.file=Adding location: [{0}]
+virtualWebappLoader.token.glob.dir=Listing files in a directory: [{0}]
+virtualWebappLoader.token.notDirectory=Path is skipped, because it does not exist or is not a directory: [{0}]
+virtualWebappLoader.token.notExists=Path is skipped, because it does not exist: [{0}]
+virtualWebappLoader.token.notFile=Path is skipped, because it does not exist or is not a file: [{0}]
+webappClassLoader.illegalJarPath=Illegal JAR entry detected with name {0}
+webappClassLoader.jdbcRemoveFailed=JDBC driver de-registration failed for web application [{0}]
+webappClassLoader.jdbcRemoveStreamError=Exception closing input stream during JDBC driver de-registration for web application [{0}]
+webappClassLoader.stopped=Illegal access: this web application instance has been stopped already.  Could not load {0}.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.
+webappClassLoader.readError=Resource read error: Could not load {0}.
+webappClassLoader.clearJdbc=The web application [{0}] registered the JDBC driver [{1}] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
+webappClassLoader.clearReferencesResourceBundlesCount=Removed [{0}] ResourceBundle references from the cache for web application [{1}]
+webappClassLoader.clearReferencesResourceBundlesFail=Failed to clear ResourceBundle references for web application [{0}]
+webappClassLoader.clearRmiInfo=Failed to find class sun.rmi.transport.Target to clear context class loader for web application [{0}]. This is expected on non-Sun JVMs.
+webappClassLoader.clearRmiFail=Failed to clear context class loader referenced from sun.rmi.transport.Target for web application [{0}]
+webappClassLoader.checkThreadLocalsForLeaks.badKey=Unable to determine string representation of key of type [{0}]
+webappClassLoader.checkThreadLocalsForLeaks.badValue=Unable to determine string representation of value of type [{0}]
+webappClassLoader.checkThreadLocalsForLeaks.unknown=Unknown
+webappClassLoader.checkThreadLocalsForLeaks=The web application [{0}] created a ThreadLocal with key of type [{1}] (value [{2}]) and a value of type [{3}] (value [{4}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak. 
+webappClassLoader.checkThreadLocalsForLeaksDebug=The web application [{0}] created a ThreadLocal with key of type [{1}] (value [{2}]). The ThreadLocal has been correctly set to null and the key will be removed by GC.
+webappClassLoader.checkThreadLocalsForLeaksFail=Failed to check for ThreadLocal references for web application [{0}]
+webappClassLoader.checkThreadsHttpClient=Found HttpClient keep-alive thread using web application class loader. Fixed by switching thread to the parent class loader.
+webappClassLoader.stopThreadFail=Failed to terminate thread named [{0}] for web application [{1}]
+webappClassLoader.stopTimerThreadFail=Failed to terminate TimerThread named [{0}] for web application [{1}]
+webappClassLoader.validationErrorJarPath=Unable to validate JAR entry with name {0}
+webappClassLoader.warnRequestThread=The web application [{0}] is still processing a request that has yet to finish. This is very likely to create a memory leak. You can control the time allowed for requests to finish by using the unloadDelay attribute of the standard Context implementation.
+webappClassLoader.warnThread=The web application [{0}] appears to have started a thread named [{1}] but has failed to stop it. This is very likely to create a memory leak.
+webappClassLoader.warnTimerThread=The web application [{0}] appears to have started a TimerThread named [{1}] via the java.util.Timer API but has failed to stop it. To prevent a memory leak, the timer (and hence the associated thread) has been forcibly canceled. 
+webappClassLoader.wrongVersion=(unable to load class {0})
+webappLoader.addRepository=Adding repository {0}
+webappLoader.deploy=Deploying class repositories to work directory {0}
+webappLoader.jarDeploy=Deploy JAR {0} to {1}
+webappLoader.classDeploy=Deploy class files {0} to {1}
+webappLoader.alreadyStarted=Loader has already been started
+webappLoader.checkInterval=Cannot set reload check interval to {0} seconds
+webappLoader.notContext=Cannot auto-reload unless our Container is a Context
+webappLoader.notReloadabe=Reloadable property is set to false
+webappLoader.notStarted=Loader has not yet been started
+webappLoader.reloadable=Cannot set reloadable property to {0}
+webappLoader.reloading=Reloading checks are enabled for this Context
+webappLoader.removeRepository=Removing repository {0}
+webappLoader.starting=Starting this Loader
+webappLoader.stopping=Stopping this Loader
+webappLoader.failModifiedCheck=Error tracking modifications
+webappLoader.copyFailure=Failed to copy resources
+webappLoader.mkdirFailure=Failed to create destination directory to copy resources
+webappLoader.namingFailure=Failed to access resource {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_es.properties
new file mode 100644
index 0000000..7b37fc8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_es.properties
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+fileClassLoader.canRead = No se puede leer el Repositorio {0}
+fileClassLoader.exists = No existe el Repositorio {0}
+fileClassLoader.jarFile = No puedo leer archivo JAR {0}
+fileClassLoader.restricted = No puedo cargar clase restringida {0}
+standardLoader.addRepository = A\u00F1adiendo repositorio {0}
+standardLoader.alreadyStarted = Ya ha sido arrancado el Cargador
+standardLoader.checkInterval = No puedo poner el intervalo de revisi\u00F3n de recarga a {0} segundos
+standardLoader.notContext = No puedo auto-recargar a menos que nuestro Contenedor sea un Contexto
+standardLoader.notReloadabe = La propiedad Recargable est\u00E1 puesta a falsa
+standardLoader.notStarted = A\u00FAn no se ha arrancado el Cargador
+standardLoader.reloadable = No puedo poner la propiedad recargable a {0}
+standardLoader.reloading = Se han activado las revisiones de Recarga para este Contexto
+standardLoader.removeRepository = Quitando repositorio {0}
+standardLoader.starting = Arrancando este Cargador
+standardLoader.stopping = Parando este Cargador
+webappClassLoader.stopped = Acceso ilegal\: esta instancia de aplicaci\u00F3n web ya ha sido parada.  Could not load {0}.  La eventual traza de pila que sigue ha sido motivada por un error lanzado con motivos de depuraci\u00F3n as\u00ED como para intentar terminar el hilo que motiv\u00F3 el acceso ilegal y no tiene impacto funcional.
+webappClassLoader.wrongVersion = (no puedo cargar clase {0})
+webappLoader.addRepository = A\u00F1adiendo repositorio {0}
+webappLoader.deploy = Desplegando repositorios de clase en directorio de trabajo {0}
+webappLoader.jarDeploy = Despliegue del JAR {0} en {1}
+webappLoader.classDeploy = Despliegue de archivos de clase {0} en {1}
+webappLoader.alreadyStarted = Ya se ha arrancado el Cargador
+webappLoader.checkInterval = No puedo poner el intervalo de revisi\u00F3n de recarga a {0} segundos
+webappLoader.notContext = No puedo auto-recargar a menos que nuestro Contenedor sea un Contexto
+webappLoader.notReloadabe = Se ha puesto la propiedad Recargable a falsa
+webappLoader.notStarted = A\u00FAn no se ha arrancado el Cargador
+webappLoader.reloadable = No puedo poner la propiedad recargable a {0}
+webappLoader.reloading = Se han activado los chequeos de Recarga para este Contexto
+webappLoader.removeRepository = Quitando repositorio {0}
+webappLoader.starting = Arrancando este Cargador
+webappLoader.stopping = Parando este Cargador
+webappLoader.failModifiedCheck = Modificaciones de pista de error
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_fr.properties
new file mode 100644
index 0000000..b804847
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_fr.properties
@@ -0,0 +1,45 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+fileClassLoader.canRead=Le r\u00e9ceptacle (repository) {0} ne peut \u00eatre lu
+fileClassLoader.exists=Le r\u00e9ceptacle (repository) {0} n''existe pas
+fileClassLoader.jarFile=Impossible de lire le fichier JAR {0}
+fileClassLoader.restricted=Impossible de charger la classe restreinte (restricted class) {0}
+standardLoader.addRepository=Ajout du r\u00e9ceptacle (repository) {0}
+standardLoader.alreadyStarted=Le chargeur (loader) a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+standardLoader.checkInterval=Impossible de r\u00e9gler l''interval de v\u00e9rification de rechargement \u00e0 {0} secondes
+standardLoader.notContext=Impossible d''auto-recharger sans que le conteneur ne soit un contexte
+standardLoader.notReloadabe=Propri\u00e9t\u00e9 de rechargement (reloadable property) mise \u00e0 faux
+standardLoader.notStarted=Le chargeur (loader) n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9
+standardLoader.reloadable=Impossible de mettre la propri\u00e9t\u00e9 de rechargement (reloadable property) \u00e0 {0}
+standardLoader.reloading=Les v\u00e9rifications de rechargement sont activ\u00e9es pour ce contexte
+standardLoader.removeRepository=Retrait du r\u00e9ceptacle (repository) {0}
+standardLoader.starting=D\u00e9marrage de ce chargeur (loader)
+standardLoader.stopping=Arr\u00eat de ce chargeur (loader)
+webappLoader.addRepository=Ajout du r\u00e9ceptacle (repository) {0}
+webappLoader.deploy=D\u00e9ploiement des classes des r\u00e9ceptacles (class repositories) vers le dossier de travail (work directory) {0}
+webappLoader.jarDeploy=D\u00e9ploiement du JAR {0} vers {1}
+webappLoader.classDeploy=D\u00e9ploiement des fichiers classes {0} vers {1}
+webappLoader.alreadyStarted=Le chargeur (loader) a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+webappLoader.checkInterval=Impossible de r\u00e9gler l''interval de v\u00e9rification de rechargement \u00e0 {0} secondes
+webappLoader.notContext=Impossible d''auto-recharger sans que le conteneur ne soit un contexte
+webappLoader.notReloadabe=Propri\u00e9t\u00e9 de rechargement (reloadable property) mise \u00e0 faux
+webappLoader.notStarted=Le chargeur (loader) n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9
+webappLoader.reloadable=Impossible de mettre la propri\u00e9t\u00e9 de rechargement (reloadable property) \u00e0 {0}
+webappLoader.reloading=Les v\u00e9rifications de rechargement sont activ\u00e9es pour ce contexte
+webappLoader.removeRepository=Retrait du r\u00e9ceptacle (repository) {0}
+webappLoader.starting=D\u00e9marrage de ce chargeur (loader)
+webappLoader.stopping=Arr\u00eat de ce chargeur (loader)
+webappLoader.failModifiedCheck=Erreur dans le suivi des modifications
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_ja.properties
new file mode 100644
index 0000000..7962a40
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/LocalStrings_ja.properties
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+fileClassLoader.canRead=\u30ea\u30dd\u30b8\u30c8\u30ea {0} \u304c\u8aad\u3081\u307e\u305b\u3093
+fileClassLoader.exists=\u30ea\u30dd\u30b8\u30c8\u30ea {0} \u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+fileClassLoader.jarFile=JAR\u30d5\u30a1\u30a4\u30eb {0} \u304c\u8aad\u3081\u307e\u305b\u3093
+fileClassLoader.restricted=\u5236\u9650\u3055\u308c\u305f\u30af\u30e9\u30b9 {0} \u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+standardLoader.addRepository=\u30ea\u30dd\u30b8\u30c8\u30ea {0} \u3092\u8ffd\u52a0\u3057\u307e\u3059
+standardLoader.alreadyStarted=\u30ed\u30fc\u30c0\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+standardLoader.checkInterval=\u518d\u30ed\u30fc\u30c9\u30c1\u30a7\u30c3\u30af\u9593\u9694\u3092{0}\u79d2\u306b\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093
+standardLoader.notContext=\u3053\u306e\u30b3\u30f3\u30c6\u30ca\u304c\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u3067\u306a\u3051\u308c\u3070\u3001\u81ea\u52d5\u518d\u30ed\u30fc\u30c9\u306f\u3067\u304d\u307e\u305b\u3093
+standardLoader.notReloadabe=reloadable\u30d7\u30ed\u30d1\u30c6\u30a3\u304cfalse\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+standardLoader.notStarted=\u30ed\u30fc\u30c0\u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+standardLoader.reloadable=reloadable\u30d7\u30ed\u30d1\u30c6\u30a3\u3092 {0} \u306b\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093
+standardLoader.reloading=\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u518d\u30ed\u30fc\u30c9\u30c1\u30a7\u30c3\u30af\u306f\u6709\u52b9\u3067\u3059
+standardLoader.removeRepository=\u30ea\u30dd\u30b8\u30c8\u30ea {0} \u3092\u524a\u9664\u3057\u307e\u3059
+standardLoader.starting=\u3053\u306e\u30ed\u30fc\u30c0\u3092\u8d77\u52d5\u3057\u307e\u3059
+standardLoader.stopping=\u3053\u306e\u30ed\u30fc\u30c0\u3092\u505c\u6b62\u3057\u307e\u3059
+webappClassLoader.stopped=\u4e0d\u6b63\u306a\u30a2\u30af\u30bb\u30b9: \u3053\u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306f\u65e2\u306b\u505c\u6b62\u3055\u308c\u3066\u3044\u307e\u3059  Could not load {0}. \u4e0d\u6b63\u306a\u30a2\u30af\u30bb\u30b9\u3092\u5f15\u304d\u8d77\u3053\u3057\u305f\u30b9\u30ec\u30c3\u30c9\u3092\u7d42\u4e86\u3055\u305b\u3001\u6295\u3052\u3089\u308c\u305f\u30a8\u30e9\u30fc\u306b\u3088\u308a\u30c7\u30d0\u30c3\u30b0\u7528\u306b\u6b21\u306e\u30b9\u30bf\u30c3\u30af\u30c8\u30ec\u30fc\u30b9\u304c\u751f\u6210\u3055\u308c\u307e\u3057\u305f\u304c\uff0c\u6a5f\u80fd\u306b\u5f71\u97ff\u306f\u3042\u308a\u307e\u305b\u3093
+webappLoader.addRepository=\u30ea\u30dd\u30b8\u30c8\u30ea {0} \u3092\u8ffd\u52a0\u3057\u307e\u3059
+webappLoader.deploy=\u30af\u30e9\u30b9\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u4f5c\u696d\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306b\u914d\u5099\u3057\u307e\u3059
+webappLoader.jarDeploy=JAR {0} \u3092 {1} \u306b\u914d\u5099\u3057\u307e\u3059
+webappLoader.classDeploy=\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb {0} \u3092 {1} \u306b\u914d\u5099\u3057\u307e\u3059
+webappLoader.alreadyStarted=\u30ed\u30fc\u30c0\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+webappLoader.checkInterval=\u518d\u30ed\u30fc\u30c9\u30c1\u30a7\u30c3\u30af\u9593\u9694\u3092{0}\u79d2\u306b\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093
+webappLoader.notContext=\u3053\u306e\u30b3\u30f3\u30c6\u30ca\u304c\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u3067\u306a\u3051\u308c\u3070\u3001\u81ea\u52d5\u518d\u30ed\u30fc\u30c9\u306f\u3067\u304d\u307e\u305b\u3093
+webappLoader.notReloadabe=reloadable\u30d7\u30ed\u30d1\u30c6\u30a3\u304cfalse\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+webappLoader.notStarted=\u30ed\u30fc\u30c0\u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+webappLoader.reloadable=reloadable\u30d7\u30ed\u30d1\u30c6\u30a3\u3092 {0} \u306b\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093
+webappLoader.reloading=\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u518d\u30ed\u30fc\u30c9\u30c1\u30a7\u30c3\u30af\u306f\u6709\u52b9\u3067\u3059
+webappLoader.removeRepository=\u30ea\u30dd\u30b8\u30c8\u30ea {0} \u3092\u524a\u9664\u3057\u307e\u3059
+webappLoader.starting=\u3053\u306e\u30ed\u30fc\u30c0\u3092\u8d77\u52d5\u3057\u307e\u3059
+webappLoader.stopping=\u3053\u306e\u30ed\u30fc\u30c0\u3092\u505c\u6b62\u3057\u307e\u3059
+webappLoader.failModifiedCheck=\u5909\u66f4\u8ffd\u8de1\u30a8\u30e9\u30fc\u3067\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/ResourceEntry.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/ResourceEntry.java
new file mode 100644
index 0000000..8a37267
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/ResourceEntry.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+import java.net.URL;
+import java.security.cert.Certificate;
+import java.util.jar.Manifest;
+
+/**
+ * Resource entry.
+ *
+ * @author Remy Maucherat
+ * @version $Id: ResourceEntry.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public class ResourceEntry {
+
+
+    /**
+     * The "last modified" time of the origin file at the time this class
+     * was loaded, in milliseconds since the epoch.
+     */
+    public long lastModified = -1;
+
+
+    /**
+     * Binary content of the resource.
+     */
+    public byte[] binaryContent = null;
+
+
+    /**
+     * Loaded class.
+     */
+    public volatile Class<?> loadedClass = null;
+
+
+    /**
+     * URL source from where the object was loaded.
+     */
+    public URL source = null;
+
+
+    /**
+     * URL of the codebase from where the object was loaded.
+     */
+    public URL codeBase = null;
+
+
+    /**
+     * Manifest (if the resource was loaded from a JAR).
+     */
+    public Manifest manifest = null;
+
+
+    /**
+     * Certificates (if the resource was loaded from a JAR).
+     */
+    public Certificate[] certificates = null;
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/StandardClassLoader.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/StandardClassLoader.java
new file mode 100644
index 0000000..b5e5aad
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/StandardClassLoader.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * Subclass implementation of <b>java.net.URLClassLoader</b>. There are no
+ * functional differences between this class and <b>java.net.URLClassLoader</b>.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: StandardClassLoader.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+
+public class StandardClassLoader
+    extends URLClassLoader
+    implements StandardClassLoaderMBean {
+
+    public StandardClassLoader(URL repositories[]) {
+        super(repositories);
+    }
+
+    public StandardClassLoader(URL repositories[], ClassLoader parent) {
+        super(repositories, parent);
+    }
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/StandardClassLoaderMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/StandardClassLoaderMBean.java
new file mode 100644
index 0000000..822fb52
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/StandardClassLoaderMBean.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+/**
+ * MBean interface for StandardClassLoader, to allow JMX remote management.
+ *
+ * @author Remy Maucherat
+ * @version $Id: StandardClassLoaderMBean.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public interface StandardClassLoaderMBean {
+    // Marker interface
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/VirtualWebappLoader.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/VirtualWebappLoader.java
new file mode 100644
index 0000000..41b1ca2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/VirtualWebappLoader.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.loader;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.apache.catalina.LifecycleException;
+
+/**
+ * A WebappLoader that allows a customized classpath to be added
+ * through configuration in context xml. Any additional classpath entry will be
+ * added to the default webapp classpath, making easy to emulate a standard
+ * webapp without the need for assembly all the webapp dependencies as jars in
+ * WEB-INF/lib.
+ *
+ * <pre>
+ * &lt;Context docBase="\webapps\mydocbase">
+ *   &lt;Loader className="org.apache.catalina.loader.VirtualWebappLoader"
+ *              virtualClasspath="/dir/classes;/somedir/somejar.jar;/somedir/*.jar"/>
+ * &lt;/Context>
+ * </pre>
+ *
+ * <p>The <code>*.jar</code> suffix can be used to include all JAR files in a
+ * certain directory. If a file or a directory does not exist, it will be
+ * skipped. 
+ * </p>
+ *
+ *
+ * @author Fabrizio Giustina
+ * @version $Id: VirtualWebappLoader.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public class VirtualWebappLoader extends WebappLoader {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( VirtualWebappLoader.class );
+
+    /**
+     * <code>;</code> separated list of additional path elements.
+     */
+    private String virtualClasspath;
+
+    /**
+     * Construct a new WebappLoader with no defined parent class loader (so that
+     * the actual parent will be the system class loader).
+     */
+    public VirtualWebappLoader() {
+        super();
+    }
+
+    /**
+     * Construct a new WebappLoader with the specified class loader to be
+     * defined as the parent of the ClassLoader we ultimately create.
+     *
+     * @param parent The parent class loader
+     */
+    public VirtualWebappLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    /**
+     * <code>virtualClasspath</code> attribute that will be automatically set
+     * from the <code>Context</code> <code>virtualClasspath</code> attribute
+     * from the context xml file.
+     * @param path <code>;</code> separated list of path elements.
+     */
+    public void setVirtualClasspath(String path) {
+        virtualClasspath = path;
+    }
+
+    /**
+     * @return Returns searchVirtualFirst.
+     */
+    public boolean getSearchVirtualFirst() {
+        return getSearchExternalFirst();
+    }
+
+    /**
+     * @param searchVirtualFirst Whether the virtual class path should be searched before the webapp
+     */
+    public void setSearchVirtualFirst(boolean searchVirtualFirst) {
+        setSearchExternalFirst(searchVirtualFirst);
+    }
+
+    /**
+     * Implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected void startInternal() throws LifecycleException {
+
+        // just add any jar/directory set in virtual classpath to the
+        // repositories list before calling start on the standard WebappLoader
+        StringTokenizer tkn = new StringTokenizer(virtualClasspath, ";");
+        Set<String> set = new LinkedHashSet<String>();
+        while (tkn.hasMoreTokens()) {
+            String token = tkn.nextToken().trim();
+
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("virtualWebappLoader.token", token));
+
+            if (token.endsWith("*.jar")) {
+                // glob
+                token = token.substring(0, token.length() - "*.jar".length());
+
+                File directory = new File(token);
+                if (!directory.isDirectory()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString(
+                                "virtualWebappLoader.token.notDirectory",
+                                directory.getAbsolutePath()));
+                    }
+                    continue;
+                }
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString(
+                            "virtualWebappLoader.token.glob.dir",
+                            directory.getAbsolutePath()));
+                }
+                String filenames[] = directory.list();
+                Arrays.sort(filenames);
+                for (int j = 0; j < filenames.length; j++) {
+                    String filename = filenames[j].toLowerCase(Locale.ENGLISH);
+                    if (!filename.endsWith(".jar"))
+                        continue;
+                    File file = new File(directory, filenames[j]);
+                    if (!file.isFile()) {
+                        if (log.isDebugEnabled()) {
+                            log.debug(sm.getString(
+                                    "virtualWebappLoader.token.notFile",
+                                    file.getAbsolutePath()));
+                        }
+                        continue;
+                    }
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString(
+                                "virtualWebappLoader.token.file",
+                                file.getAbsolutePath()));
+                    }
+                    set.add(file.toURI().toString());
+                }
+            } else {
+                // single file or directory
+                File file = new File(token);
+                if (!file.exists()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString(
+                                "virtualWebappLoader.token.notExists",
+                                file.getAbsolutePath()));
+                    }
+                    continue;
+                }
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString(
+                            "virtualWebappLoader.token.file",
+                            file.getAbsolutePath()));
+                }
+                set.add(file.toURI().toString());
+            }
+        }
+
+        for (String repository: set) {
+            addRepository(repository);
+        }
+
+        super.startInternal();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/WebappClassLoader.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/WebappClassLoader.java
new file mode 100644
index 0000000..6dd91df
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/WebappClassLoader.java
@@ -0,0 +1,3380 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.Vector;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.jar.Attributes;
+import java.util.jar.Attributes.Name;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import javax.naming.Binding;
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.naming.JndiPermission;
+import org.apache.naming.resources.ProxyDirContext;
+import org.apache.naming.resources.Resource;
+import org.apache.naming.resources.ResourceAttributes;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Specialized web application class loader.
+ * <p>
+ * This class loader is a full reimplementation of the 
+ * <code>URLClassLoader</code> from the JDK. It is designed to be fully
+ * compatible with a normal <code>URLClassLoader</code>, although its internal
+ * behavior may be completely different.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows 
+ * the delegation model recommended in the specification. The system class 
+ * loader will be queried first, then the local repositories, and only then 
+ * delegation to the parent class loader will occur. This allows the web 
+ * application to override any shared class except the classes from J2SE.
+ * Special handling is provided from the JAXP XML parser interfaces, the JNDI
+ * interfaces, and the classes from the servlet API, which are never loaded 
+ * from the webapp repository.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper 
+ * compilation technology, any repository which contains classes from 
+ * the servlet API will be ignored by the class loader.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
+ * URLs which include the full JAR URL when a class is loaded from a JAR file,
+ * which allows setting security permission at the class level, even when a
+ * class is contained inside a JAR.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
+ * the order they are added via the initial constructor and/or any subsequent
+ * calls to <code>addRepository()</code> or <code>addJar()</code>.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
+ * security is made unless a security manager is present.
+ * <p>
+ * TODO: Is there any requirement to provide a proper Lifecycle implementation
+ *       rather than the current stubbed implementation?
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Id: WebappClassLoader.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public class WebappClassLoader
+    extends URLClassLoader
+    implements Lifecycle
+ {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
+
+    /**
+     * List of ThreadGroup names to ignore when scanning for web application
+     * started threads that need to be shut down.
+     */
+    private static final List<String> JVM_THREAD_GROUP_NAMES =
+        new ArrayList<String>();
+
+    private static final String JVN_THREAD_GROUP_SYSTEM = "system";
+    
+    static {
+        JVM_THREAD_GROUP_NAMES.add(JVN_THREAD_GROUP_SYSTEM);
+        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
+    }
+
+    protected class PrivilegedFindResourceByName
+        implements PrivilegedAction<ResourceEntry> {
+
+        protected String name;
+        protected String path;
+
+        PrivilegedFindResourceByName(String name, String path) {
+            this.name = name;
+            this.path = path;
+        }
+
+        @Override
+        public ResourceEntry run() {
+            return findResourceInternal(name, path);
+        }
+
+    }
+
+    
+    protected static final class PrivilegedGetClassLoader
+        implements PrivilegedAction<ClassLoader> {
+
+        public Class<?> clazz;
+
+        public PrivilegedGetClassLoader(Class<?> clazz){
+            this.clazz = clazz;
+        }
+
+        @Override
+        public ClassLoader run() {       
+            return clazz.getClassLoader();
+        }           
+    }
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * The set of trigger classes that will cause a proposed repository not
+     * to be added if this class is visible to the class loader that loaded
+     * this factory class.  Typically, trigger classes will be listed for
+     * components that have been integrated into the JDK for later versions,
+     * but where the corresponding JAR files are required to run on
+     * earlier versions.
+     */
+    protected static final String[] triggers = {
+        "javax.servlet.Servlet"                     // Servlet API
+    };
+
+
+    /**
+     * Set of package names which are not allowed to be loaded from a webapp
+     * class loader without delegating first.
+     */
+    protected static final String[] packageTriggers = {
+    };
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    
+    /**
+     * Use anti JAR locking code, which does URL rerouting when accessing
+     * resources.
+     */
+    boolean antiJARLocking = false; 
+    
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and no
+     * parent ClassLoader.
+     */
+    public WebappClassLoader() {
+
+        super(new URL[0]);
+        this.parent = getParent();
+        system = getSystemClassLoader();
+        securityManager = System.getSecurityManager();
+
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+
+    }
+
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and the given
+     * parent ClassLoader.
+     *
+     * @param parent Our parent class loader
+     */
+    public WebappClassLoader(ClassLoader parent) {
+
+        super(new URL[0], parent);
+                
+        this.parent = getParent();
+        
+        system = getSystemClassLoader();
+        securityManager = System.getSecurityManager();
+
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated directory context giving access to the resources in this
+     * webapp.
+     */
+    protected DirContext resources = null;
+
+
+    /**
+     * The cache of ResourceEntry for classes and resources we have loaded,
+     * keyed by resource name.
+     */
+    protected HashMap<String, ResourceEntry> resourceEntries = new HashMap<String, ResourceEntry>();
+
+
+    /**
+     * The list of not found resources.
+     */
+    protected HashMap<String, String> notFoundResources =
+        new LinkedHashMap<String, String>() {
+        private static final long serialVersionUID = 1L;
+        @Override
+        protected boolean removeEldestEntry(
+                Map.Entry<String, String> eldest) {
+            return size() > 1000;
+        }
+    };
+
+
+    /**
+     * Should this class loader delegate to the parent class loader
+     * <strong>before</strong> searching its own repositories (i.e. the
+     * usual Java2 delegation model)?  If set to <code>false</code>,
+     * this class loader will search its own repositories first, and
+     * delegate to the parent only if the class or resource is not
+     * found locally.
+     */
+    protected boolean delegate = false;
+
+
+    /**
+     * Last time a JAR was accessed.
+     */
+    protected long lastJarAccessed = 0L;
+
+
+    /**
+     * The list of local repositories, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected String[] repositories = new String[0];
+
+
+     /**
+      * Repositories URLs, used to cache the result of getURLs.
+      */
+     protected URL[] repositoryURLs = null;
+
+
+    /**
+     * Repositories translated as path in the work directory (for Jasper
+     * originally), but which is used to generate fake URLs should getURLs be
+     * called.
+     */
+    protected File[] files = new File[0];
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected JarFile[] jarFiles = new JarFile[0];
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected File[] jarRealFiles = new File[0];
+
+
+    /**
+     * The path which will be monitored for added Jar files.
+     */
+    protected String jarPath = null;
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected String[] jarNames = new String[0];
+
+
+    /**
+     * The list of JARs last modified dates, in the order they should be
+     * searched for locally loaded classes or resources.
+     */
+    protected long[] lastModifiedDates = new long[0];
+
+
+    /**
+     * The list of resources which should be checked when checking for
+     * modifications.
+     */
+    protected String[] paths = new String[0];
+
+
+    /**
+     * A list of read File and Jndi Permission's required if this loader
+     * is for a web application context.
+     */
+    protected ArrayList<Permission> permissionList =
+        new ArrayList<Permission>();
+
+
+    /**
+     * Path where resources loaded from JARs will be extracted.
+     */
+    protected File loaderDir = null;
+    protected String canonicalLoaderDir = null;
+
+    /**
+     * The PermissionCollection for each CodeSource for a web
+     * application context.
+     */
+    protected HashMap<String, PermissionCollection> loaderPC = new HashMap<String, PermissionCollection>();
+
+
+    /**
+     * Instance of the SecurityManager installed.
+     */
+    protected SecurityManager securityManager = null;
+
+
+    /**
+     * The parent class loader.
+     */
+    protected ClassLoader parent = null;
+
+
+    /**
+     * The system class loader.
+     */
+    protected ClassLoader system = null;
+
+
+    /**
+     * Has this component been started?
+     */
+    protected boolean started = false;
+
+
+    /**
+     * Has external repositories.
+     */
+    protected boolean hasExternalRepositories = false;
+
+    /**
+     * Search external repositories first
+     */
+    protected boolean searchExternalFirst = false;
+
+    /**
+     * need conversion for properties files
+     */
+    protected boolean needConvert = false;
+
+
+    /**
+     * All permission.
+     */
+    protected Permission allPermission = new java.security.AllPermission();
+
+
+    /**
+     * Should Tomcat attempt to null out any static or final fields from loaded
+     * classes when a web application is stopped as a work around for apparent
+     * garbage collection bugs and application coding errors? There have been
+     * some issues reported with log4j when this option is true. Applications
+     * without memory leaks using recent JVMs should operate correctly with this
+     * option set to <code>false</code>. If not specified, the default value of
+     * <code>false</code> will be used. 
+     */
+    private boolean clearReferencesStatic = false;
+
+    /**
+     * Should Tomcat attempt to terminate threads that have been started by the
+     * web application? Stopping threads is performed via the deprecated (for
+     * good reason) <code>Thread.stop()</code> method and is likely to result in
+     * instability. As such, enabling this should be viewed as an option of last
+     * resort in a development environment and is not recommended in a
+     * production environment. If not specified, the default value of
+     * <code>false</code> will be used.
+     */
+    private boolean clearReferencesStopThreads = false;
+
+    /**
+     * Should Tomcat attempt to terminate any {@link java.util.TimerThread}s
+     * that have been started by the web application? If not specified, the
+     * default value of <code>false</code> will be used.
+     */
+    private boolean clearReferencesStopTimerThreads = false;
+
+    /**
+     * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
+     * when the class loader is stopped? If not specified, the default value
+     * of <code>true</code> is used. Changing the default setting is likely to
+     * lead to memory leaks and other issues.
+     */
+    private boolean clearReferencesLogFactoryRelease = true;
+
+    /**
+     * If an HttpClient keep-alive timer thread has been started by this web
+     * application and is still running, should Tomcat change the context class
+     * loader from the current {@link WebappClassLoader} to
+     * {@link WebappClassLoader#parent} to prevent a memory leak? Note that the
+     * keep-alive timer thread will stop on its own once the keep-alives all
+     * expire however, on a busy system that might not happen for some time.
+     */
+    private boolean clearReferencesHttpClientKeepAliveThread = true;
+    
+    /**
+     * Name of associated context used with logging and JMX to associate with
+     * the right web application. Particularly useful for the clear references
+     * messages. Defaults to unknown but if standard Tomcat components are used
+     * it will be updated during initialisation from the resources.
+     */
+    private String contextName = "unknown";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Get associated resources.
+     */
+    public DirContext getResources() {
+
+        return this.resources;
+
+    }
+
+
+    /**
+     * Set associated resources.
+     */
+    public void setResources(DirContext resources) {
+
+        this.resources = resources;
+
+        if (resources instanceof ProxyDirContext) {
+            contextName = ((ProxyDirContext) resources).getContextName();
+        }
+    }
+
+
+    /**
+     * Return the context name for this class loader.
+     */
+    public String getContextName() {
+
+        return (this.contextName);
+
+    }
+
+
+    /**
+     * Return the "delegate first" flag for this class loader.
+     */
+    public boolean getDelegate() {
+
+        return (this.delegate);
+
+    }
+
+
+    /**
+     * Set the "delegate first" flag for this class loader.
+     *
+     * @param delegate The new "delegate first" flag
+     */
+    public void setDelegate(boolean delegate) {
+
+        this.delegate = delegate;
+
+    }
+
+
+    /**
+     * @return Returns the antiJARLocking.
+     */
+    public boolean getAntiJARLocking() {
+        return antiJARLocking;
+    }
+    
+    
+    /**
+     * @param antiJARLocking The antiJARLocking to set.
+     */
+    public void setAntiJARLocking(boolean antiJARLocking) {
+        this.antiJARLocking = antiJARLocking;
+    }
+
+    /**
+     * @return Returns the searchExternalFirst.
+     */
+    public boolean getSearchExternalFirst() {
+        return searchExternalFirst;
+    }
+
+    /**
+     * @param searchExternalFirst Whether external repositories should be searched first
+     */
+    public void setSearchExternalFirst(boolean searchExternalFirst) {
+        this.searchExternalFirst = searchExternalFirst;
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a read FilePermission
+     * or JndiPermission for the file directory path.
+     *
+     * @param filepath file directory path
+     */
+    public void addPermission(String filepath) {
+        if (filepath == null) {
+            return;
+        }
+
+        String path = filepath;
+
+        if (securityManager != null) {
+            Permission permission = null;
+            if (path.startsWith("jndi:") || path.startsWith("jar:jndi:")) {
+                if (!path.endsWith("/")) {
+                    path = path + "/";
+                }
+                permission = new JndiPermission(path + "*");
+                addPermission(permission);
+            } else {
+                if (!path.endsWith(File.separator)) {
+                    permission = new FilePermission(path, "read");
+                    addPermission(permission);
+                    path = path + File.separator;
+                }
+                permission = new FilePermission(path + "-", "read");
+                addPermission(permission);
+            }
+        }
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a read FilePermission
+     * or JndiPermission for URL.
+     *
+     * @param url URL for a file or directory on local system
+     */
+    public void addPermission(URL url) {
+        if (url != null) {
+            addPermission(url.toString());
+        }
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a Permission.
+     *
+     * @param permission The permission
+     */
+    public void addPermission(Permission permission) {
+        if ((securityManager != null) && (permission != null)) {
+            permissionList.add(permission);
+        }
+    }
+
+
+    /**
+     * Return the JAR path.
+     */
+    public String getJarPath() {
+
+        return this.jarPath;
+
+    }
+
+
+    /**
+     * Change the Jar path.
+     */
+    public void setJarPath(String jarPath) {
+
+        this.jarPath = jarPath;
+
+    }
+
+
+    /**
+     * Change the work directory.
+     */
+    public void setWorkDir(File workDir) {
+        this.loaderDir = new File(workDir, "loader");
+        if (loaderDir == null) {
+            canonicalLoaderDir = null;
+        } else { 
+            try {
+                canonicalLoaderDir = loaderDir.getCanonicalPath();
+                if (!canonicalLoaderDir.endsWith(File.separator)) {
+                    canonicalLoaderDir += File.separator;
+                }
+            } catch (IOException ioe) {
+                canonicalLoaderDir = null;
+            }
+        }
+    }
+
+     /**
+      * Utility method for use in subclasses.
+      * Must be called before Lifecycle methods to have any effect.
+      */
+     protected void setParentClassLoader(ClassLoader pcl) {
+         parent = pcl;
+     }
+
+     /**
+      * Return the clearReferencesStatic flag for this Context.
+      */
+     public boolean getClearReferencesStatic() {
+         return (this.clearReferencesStatic);
+     }
+
+
+     /**
+      * Set the clearReferencesStatic feature for this Context.
+      *
+      * @param clearReferencesStatic The new flag value
+      */
+     public void setClearReferencesStatic(boolean clearReferencesStatic) {
+         this.clearReferencesStatic = clearReferencesStatic;
+     }
+
+
+     /**
+      * Return the clearReferencesStopThreads flag for this Context.
+      */
+     public boolean getClearReferencesStopThreads() {
+         return (this.clearReferencesStopThreads);
+     }
+
+
+     /**
+      * Set the clearReferencesStopThreads feature for this Context.
+      *
+      * @param clearReferencesStopThreads The new flag value
+      */
+     public void setClearReferencesStopThreads(
+             boolean clearReferencesStopThreads) {
+         this.clearReferencesStopThreads = clearReferencesStopThreads;
+     }
+
+
+     /**
+      * Return the clearReferencesStopTimerThreads flag for this Context.
+      */
+     public boolean getClearReferencesStopTimerThreads() {
+         return (this.clearReferencesStopTimerThreads);
+     }
+
+
+     /**
+      * Set the clearReferencesStopTimerThreads feature for this Context.
+      *
+      * @param clearReferencesStopTimerThreads The new flag value
+      */
+     public void setClearReferencesStopTimerThreads(
+             boolean clearReferencesStopTimerThreads) {
+         this.clearReferencesStopTimerThreads = clearReferencesStopTimerThreads;
+     }
+
+
+     /**
+      * Return the clearReferencesLogFactoryRelease flag for this Context.
+      */
+     public boolean getClearReferencesLogFactoryRelease() {
+         return (this.clearReferencesLogFactoryRelease);
+     }
+
+
+     /**
+      * Set the clearReferencesLogFactoryRelease feature for this Context.
+      *
+      * @param clearReferencesLogFactoryRelease The new flag value
+      */
+     public void setClearReferencesLogFactoryRelease(
+             boolean clearReferencesLogFactoryRelease) {
+         this.clearReferencesLogFactoryRelease =
+             clearReferencesLogFactoryRelease;
+     }
+
+
+     /**
+      * Return the clearReferencesHttpClientKeepAliveThread flag for this
+      * Context.
+      */
+     public boolean getClearReferencesHttpClientKeepAliveThread() {
+         return (this.clearReferencesHttpClientKeepAliveThread);
+     }
+
+
+     /**
+      * Set the clearReferencesHttpClientKeepAliveThread feature for this
+      * Context.
+      *
+      * @param clearReferencesHttpClientKeepAliveThread The new flag value
+      */
+     public void setClearReferencesHttpClientKeepAliveThread(
+             boolean clearReferencesHttpClientKeepAliveThread) {
+         this.clearReferencesHttpClientKeepAliveThread =
+             clearReferencesHttpClientKeepAliveThread;
+     }
+
+     
+    // ------------------------------------------------------- Reloader Methods
+
+
+    /**
+     * Add a new repository to the set of places this ClassLoader can look for
+     * classes to be loaded.
+     *
+     * @param repository Name of a source of classes to be loaded, such as a
+     *  directory pathname, a JAR file pathname, or a ZIP file pathname
+     *
+     * @exception IllegalArgumentException if the specified repository is
+     *  invalid or does not exist
+     */
+    public void addRepository(String repository) {
+
+        // Ignore any of the standard repositories, as they are set up using
+        // either addJar or addRepository
+        if (repository.startsWith("/WEB-INF/lib")
+            || repository.startsWith("/WEB-INF/classes"))
+            return;
+
+        // Add this repository to our underlying class loader
+        try {
+            URL url = new URL(repository);
+            super.addURL(url);
+            hasExternalRepositories = true;
+            repositoryURLs = null;
+        } catch (MalformedURLException e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Invalid repository: " + repository); 
+            iae.initCause(e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Add a new repository to the set of places this ClassLoader can look for
+     * classes to be loaded.
+     *
+     * @param repository Name of a source of classes to be loaded, such as a
+     *  directory pathname, a JAR file pathname, or a ZIP file pathname
+     *
+     * @exception IllegalArgumentException if the specified repository is
+     *  invalid or does not exist
+     */
+    synchronized void addRepository(String repository, File file) {
+
+        // Note : There should be only one (of course), but I think we should
+        // keep this a bit generic
+
+        if (repository == null)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug("addRepository(" + repository + ")");
+
+        int i;
+
+        // Add this repository to our internal list
+        String[] result = new String[repositories.length + 1];
+        for (i = 0; i < repositories.length; i++) {
+            result[i] = repositories[i];
+        }
+        result[repositories.length] = repository;
+        repositories = result;
+
+        // Add the file to the list
+        File[] result2 = new File[files.length + 1];
+        for (i = 0; i < files.length; i++) {
+            result2[i] = files[i];
+        }
+        result2[files.length] = file;
+        files = result2;
+
+    }
+
+
+    synchronized void addJar(String jar, JarFile jarFile, File file)
+        throws IOException {
+
+        if (jar == null)
+            return;
+        if (jarFile == null)
+            return;
+        if (file == null)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug("addJar(" + jar + ")");
+
+        int i;
+
+        if ((jarPath != null) && (jar.startsWith(jarPath))) {
+
+            String jarName = jar.substring(jarPath.length());
+            while (jarName.startsWith("/"))
+                jarName = jarName.substring(1);
+
+            String[] result = new String[jarNames.length + 1];
+            for (i = 0; i < jarNames.length; i++) {
+                result[i] = jarNames[i];
+            }
+            result[jarNames.length] = jarName;
+            jarNames = result;
+
+        }
+
+        try {
+
+            // Register the JAR for tracking
+
+            long lastModified =
+                ((ResourceAttributes) resources.getAttributes(jar))
+                .getLastModified();
+
+            String[] result = new String[paths.length + 1];
+            for (i = 0; i < paths.length; i++) {
+                result[i] = paths[i];
+            }
+            result[paths.length] = jar;
+            paths = result;
+
+            long[] result3 = new long[lastModifiedDates.length + 1];
+            for (i = 0; i < lastModifiedDates.length; i++) {
+                result3[i] = lastModifiedDates[i];
+            }
+            result3[lastModifiedDates.length] = lastModified;
+            lastModifiedDates = result3;
+
+        } catch (NamingException e) {
+            // Ignore
+        }
+
+        // If the JAR currently contains invalid classes, don't actually use it
+        // for classloading
+        if (!validateJarFile(file))
+            return;
+
+        JarFile[] result2 = new JarFile[jarFiles.length + 1];
+        for (i = 0; i < jarFiles.length; i++) {
+            result2[i] = jarFiles[i];
+        }
+        result2[jarFiles.length] = jarFile;
+        jarFiles = result2;
+
+        // Add the file to the list
+        File[] result4 = new File[jarRealFiles.length + 1];
+        for (i = 0; i < jarRealFiles.length; i++) {
+            result4[i] = jarRealFiles[i];
+        }
+        result4[jarRealFiles.length] = file;
+        jarRealFiles = result4;
+    }
+
+
+    /**
+     * Return a String array of the current repositories for this class
+     * loader.  If there are no repositories, a zero-length array is
+     * returned.For security reason, returns a clone of the Array (since 
+     * String are immutable).
+     */
+    public String[] findRepositories() {
+
+        return (repositories.clone());
+
+    }
+
+
+    /**
+     * Have one or more classes or resources been modified so that a reload
+     * is appropriate?
+     */
+    public boolean modified() {
+
+        if (log.isDebugEnabled())
+            log.debug("modified()");
+
+        // Checking for modified loaded resources
+        int length = paths.length;
+
+        // A rare race condition can occur in the updates of the two arrays
+        // It's totally ok if the latest class added is not checked (it will
+        // be checked the next time
+        int length2 = lastModifiedDates.length;
+        if (length > length2)
+            length = length2;
+
+        for (int i = 0; i < length; i++) {
+            try {
+                long lastModified =
+                    ((ResourceAttributes) resources.getAttributes(paths[i]))
+                    .getLastModified();
+                if (lastModified != lastModifiedDates[i]) {
+                    if( log.isDebugEnabled() ) 
+                        log.debug("  Resource '" + paths[i]
+                                  + "' was modified; Date is now: "
+                                  + new java.util.Date(lastModified) + " Was: "
+                                  + new java.util.Date(lastModifiedDates[i]));
+                    return (true);
+                }
+            } catch (NamingException e) {
+                log.error("    Resource '" + paths[i] + "' is missing");
+                return (true);
+            }
+        }
+
+        length = jarNames.length;
+
+        // Check if JARs have been added or removed
+        if (getJarPath() != null) {
+
+            try {
+                NamingEnumeration<Binding> enumeration =
+                    resources.listBindings(getJarPath());
+                int i = 0;
+                while (enumeration.hasMoreElements() && (i < length)) {
+                    NameClassPair ncPair = enumeration.nextElement();
+                    String name = ncPair.getName();
+                    // Ignore non JARs present in the lib folder
+                    if (!name.endsWith(".jar"))
+                        continue;
+                    if (!name.equals(jarNames[i])) {
+                        // Missing JAR
+                        log.info("    Additional JARs have been added : '" 
+                                 + name + "'");
+                        return (true);
+                    }
+                    i++;
+                }
+                if (enumeration.hasMoreElements()) {
+                    while (enumeration.hasMoreElements()) {
+                        NameClassPair ncPair = enumeration.nextElement();
+                        String name = ncPair.getName();
+                        // Additional non-JAR files are allowed
+                        if (name.endsWith(".jar")) {
+                            // There was more JARs
+                            log.info("    Additional JARs have been added");
+                            return (true);
+                        }
+                    }
+                } else if (i < jarNames.length) {
+                    // There was less JARs
+                    log.info("    Additional JARs have been added");
+                    return (true);
+                }
+            } catch (NamingException e) {
+                if (log.isDebugEnabled())
+                    log.debug("    Failed tracking modifications of '"
+                        + getJarPath() + "'");
+            } catch (ClassCastException e) {
+                log.error("    Failed tracking modifications of '"
+                          + getJarPath() + "' : " + e.getMessage());
+            }
+
+        }
+
+        // No classes have been modified
+        return (false);
+
+    }
+
+
+    /**
+     * Render a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("WebappClassLoader\r\n");
+        sb.append("  context: ");
+        sb.append(contextName);
+        sb.append("\r\n");
+        sb.append("  delegate: ");
+        sb.append(delegate);
+        sb.append("\r\n");
+        sb.append("  repositories:\r\n");
+        if (repositories != null) {
+            for (int i = 0; i < repositories.length; i++) {
+                sb.append("    ");
+                sb.append(repositories[i]);
+                sb.append("\r\n");
+            }
+        }
+        if (this.parent != null) {
+            sb.append("----------> Parent Classloader:\r\n");
+            sb.append(this.parent.toString());
+            sb.append("\r\n");
+        }
+        return (sb.toString());
+
+    }
+
+
+    // ---------------------------------------------------- ClassLoader Methods
+
+
+     /**
+      * Add the specified URL to the classloader.
+      */
+    @Override
+     protected void addURL(URL url) {
+         super.addURL(url);
+         hasExternalRepositories = true;
+         repositoryURLs = null;
+     }
+
+
+    /**
+     * Find the specified class in our local repositories, if possible.  If
+     * not found, throw <code>ClassNotFoundException</code>.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    @Override
+    public Class<?> findClass(String name) throws ClassNotFoundException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findClass(" + name + ")");
+
+        // Cannot load anything from local repositories if class loader is stopped
+        if (!started) {
+            throw new ClassNotFoundException(name);
+        }
+
+        // (1) Permission to define this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    if (log.isTraceEnabled())
+                        log.trace("      securityManager.checkPackageDefinition");
+                    securityManager.checkPackageDefinition(name.substring(0,i));
+                } catch (Exception se) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->Exception-->ClassNotFoundException", se);
+                    throw new ClassNotFoundException(name, se);
+                }
+            }
+        }
+
+        // Ask our superclass to locate this class, if possible
+        // (throws ClassNotFoundException if it is not found)
+        Class<?> clazz = null;
+        try {
+            if (log.isTraceEnabled())
+                log.trace("      findClassInternal(" + name + ")");
+            if (hasExternalRepositories && searchExternalFirst) {
+                try {
+                    clazz = super.findClass(name);
+                } catch(ClassNotFoundException cnfe) {
+                    // Ignore - will search internal repositories next
+                } catch(AccessControlException ace) {
+                    log.warn("WebappClassLoader.findClassInternal(" + name
+                            + ") security exception: " + ace.getMessage(), ace);
+                    throw new ClassNotFoundException(name, ace);
+                } catch (RuntimeException e) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->RuntimeException Rethrown", e);
+                    throw e;
+                }
+            }
+            if ((clazz == null)) {
+                try {
+                    clazz = findClassInternal(name);
+                } catch(ClassNotFoundException cnfe) {
+                    if (!hasExternalRepositories || searchExternalFirst) {
+                        throw cnfe;
+                    }
+                } catch(AccessControlException ace) {
+                    log.warn("WebappClassLoader.findClassInternal(" + name
+                            + ") security exception: " + ace.getMessage(), ace);
+                    throw new ClassNotFoundException(name, ace);
+                } catch (RuntimeException e) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->RuntimeException Rethrown", e);
+                    throw e;
+                }
+            }
+            if ((clazz == null) && hasExternalRepositories && !searchExternalFirst) {
+                try {
+                    clazz = super.findClass(name);
+                } catch(AccessControlException ace) {
+                    log.warn("WebappClassLoader.findClassInternal(" + name
+                            + ") security exception: " + ace.getMessage(), ace);
+                    throw new ClassNotFoundException(name, ace);
+                } catch (RuntimeException e) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->RuntimeException Rethrown", e);
+                    throw e;
+                }
+            }
+            if (clazz == null) {
+                if (log.isDebugEnabled())
+                    log.debug("    --> Returning ClassNotFoundException");
+                throw new ClassNotFoundException(name);
+            }
+        } catch (ClassNotFoundException e) {
+            if (log.isTraceEnabled())
+                log.trace("    --> Passing on ClassNotFoundException");
+            throw e;
+        }
+
+        // Return the class we have located
+        if (log.isTraceEnabled())
+            log.debug("      Returning class " + clazz);
+        
+        if (log.isTraceEnabled()) {
+            ClassLoader cl;
+            if (Globals.IS_SECURITY_ENABLED){
+                cl = AccessController.doPrivileged(
+                    new PrivilegedGetClassLoader(clazz));
+            } else {
+                cl = clazz.getClassLoader();
+            }
+            log.debug("      Loaded by " + cl.toString());
+        }
+        return (clazz);
+
+    }
+
+
+    /**
+     * Find the specified resource in our local repository, and return a
+     * <code>URL</code> referring to it, or <code>null</code> if this resource
+     * cannot be found.
+     *
+     * @param name Name of the resource to be found
+     */
+    @Override
+    public URL findResource(final String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResource(" + name + ")");
+
+        URL url = null;
+
+        if (hasExternalRepositories && searchExternalFirst)
+            url = super.findResource(name);
+
+        if (url == null) {
+            ResourceEntry entry = resourceEntries.get(name);
+            if (entry == null) {
+                if (securityManager != null) {
+                    PrivilegedAction<ResourceEntry> dp =
+                        new PrivilegedFindResourceByName(name, name);
+                    entry = AccessController.doPrivileged(dp);
+                } else {
+                    entry = findResourceInternal(name, name);
+                }
+            }
+            if (entry != null) {
+                url = entry.source;
+            }
+        }
+
+        if ((url == null) && hasExternalRepositories && !searchExternalFirst)
+            url = super.findResource(name);
+
+        if (log.isDebugEnabled()) {
+            if (url != null)
+                log.debug("    --> Returning '" + url.toString() + "'");
+            else
+                log.debug("    --> Resource not found, returning null");
+        }
+        return (url);
+
+    }
+
+
+    /**
+     * Return an enumeration of <code>URLs</code> representing all of the
+     * resources with the given name.  If no resources with this name are
+     * found, return an empty enumeration.
+     *
+     * @param name Name of the resources to be found
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public Enumeration<URL> findResources(String name) throws IOException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResources(" + name + ")");
+
+        Vector<URL> result = new Vector<URL>();
+
+        int jarFilesLength = jarFiles.length;
+        int repositoriesLength = repositories.length;
+
+        int i;
+
+        // Adding the results of a call to the superclass
+        if (hasExternalRepositories && searchExternalFirst) {
+
+            Enumeration<URL> otherResourcePaths = super.findResources(name);
+
+            while (otherResourcePaths.hasMoreElements()) {
+                result.addElement(otherResourcePaths.nextElement());
+            }
+
+        }
+        // Looking at the repositories
+        for (i = 0; i < repositoriesLength; i++) {
+            try {
+                String fullPath = repositories[i] + name;
+                resources.lookup(fullPath);
+                // Note : Not getting an exception here means the resource was
+                // found
+                try {
+                    result.addElement(getURI(new File(files[i], name)));
+                } catch (MalformedURLException e) {
+                    // Ignore
+                }
+            } catch (NamingException e) {
+                // Ignore
+            }
+        }
+
+        // Looking at the JAR files
+        synchronized (jarFiles) {
+            if (openJARs()) {
+                for (i = 0; i < jarFilesLength; i++) {
+                    JarEntry jarEntry = jarFiles[i].getJarEntry(name);
+                    if (jarEntry != null) {
+                        try {
+                            String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+                            jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
+                            result.addElement(new URL(jarFakeUrl));
+                        } catch (MalformedURLException e) {
+                            // Ignore
+                        }
+                    }
+                }
+            }
+        }
+
+        // Adding the results of a call to the superclass
+        if (hasExternalRepositories && !searchExternalFirst) {
+
+            Enumeration<URL> otherResourcePaths = super.findResources(name);
+
+            while (otherResourcePaths.hasMoreElements()) {
+                result.addElement(otherResourcePaths.nextElement());
+            }
+
+        }
+
+        return result.elements();
+
+    }
+
+
+    /**
+     * Find the resource with the given name.  A resource is some data
+     * (images, audio, text, etc.) that can be accessed by class code in a
+     * way that is independent of the location of the code.  The name of a
+     * resource is a "/"-separated path name that identifies the resource.
+     * If the resource cannot be found, return <code>null</code>.
+     * <p>
+     * This method searches according to the following algorithm, returning
+     * as soon as it finds the appropriate URL.  If the resource cannot be
+     * found, returns <code>null</code>.
+     * <ul>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findResource()</code> to find this resource in our
+     *     locally defined repositories.</li>
+     * <li>Call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * </ul>
+     *
+     * @param name Name of the resource to return a URL for
+     */
+    @Override
+    public URL getResource(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResource(" + name + ")");
+        URL url = null;
+
+        // (1) Delegate to parent if requested
+        if (delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            url = loader.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return (url);
+            }
+        }
+
+        // (2) Search local repositories
+        url = findResource(name);
+        if (url != null) {
+            // Locating the repository for special handling in the case 
+            // of a JAR
+            if (antiJARLocking) {
+                ResourceEntry entry = resourceEntries.get(name);
+                try {
+                    String repository = entry.codeBase.toString();
+                    if ((repository.endsWith(".jar")) 
+                            && (!(name.endsWith(".class")))) {
+                        // Copy binary content to the work directory if not present
+                        File resourceFile = new File(loaderDir, name);
+                        url = getURI(resourceFile);
+                    }
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning '" + url.toString() + "'");
+            return (url);
+        }
+
+        // (3) Delegate to parent unconditionally if not already attempted
+        if( !delegate ) {
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            url = loader.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return (url);
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return (null);
+
+    }
+
+
+    /**
+     * Find the resource with the given name, and return an input stream
+     * that can be used for reading it.  The search order is as described
+     * for <code>getResource()</code>, after checking to see if the resource
+     * data has been previously cached.  If the resource cannot be found,
+     * return <code>null</code>.
+     *
+     * @param name Name of the resource to return an input stream for
+     */
+    @Override
+    public InputStream getResourceAsStream(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResourceAsStream(" + name + ")");
+        InputStream stream = null;
+
+        // (0) Check for a cached copy of this resource
+        stream = findLoadedResource(name);
+        if (stream != null) {
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning stream from cache");
+            return (stream);
+        }
+
+        // (1) Delegate to parent if requested
+        if (delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            stream = loader.getResourceAsStream(name);
+            if (stream != null) {
+                // FIXME - cache???
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return (stream);
+            }
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        URL url = findResource(name);
+        if (url != null) {
+            // FIXME - cache???
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning stream from local");
+            stream = findLoadedResource(name);
+            try {
+                if (hasExternalRepositories && (stream == null))
+                    stream = url.openStream();
+            } catch (IOException e) {
+                // Ignore
+            }
+            if (stream != null)
+                return (stream);
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader unconditionally " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            stream = loader.getResourceAsStream(name);
+            if (stream != null) {
+                // FIXME - cache???
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return (stream);
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return (null);
+
+    }
+
+
+    /**
+     * Load the class with the specified name.  This method searches for
+     * classes in the same manner as <code>loadClass(String, boolean)</code>
+     * with <code>false</code> as the second argument.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+
+        return (loadClass(name, false));
+
+    }
+
+
+    /**
+     * Load the class with the specified name, searching using the following
+     * algorithm until it finds and returns the class.  If the class cannot
+     * be found, returns <code>ClassNotFoundException</code>.
+     * <ul>
+     * <li>Call <code>findLoadedClass(String)</code> to check if the
+     *     class has already been loaded.  If it has, the same
+     *     <code>Class</code> object is returned.</li>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>loadClass()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findClass()</code> to find this class in our locally
+     *     defined repositories.</li>
+     * <li>Call the <code>loadClass()</code> method of our parent
+     *     class loader, if any.</li>
+     * </ul>
+     * If the class was found using the above steps, and the
+     * <code>resolve</code> flag is <code>true</code>, this method will then
+     * call <code>resolveClass(Class)</code> on the resulting Class object.
+     *
+     * @param name Name of the class to be loaded
+     * @param resolve If <code>true</code> then resolve the class
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    @Override
+    public synchronized Class<?> loadClass(String name, boolean resolve)
+        throws ClassNotFoundException {
+
+        if (log.isDebugEnabled())
+            log.debug("loadClass(" + name + ", " + resolve + ")");
+        Class<?> clazz = null;
+
+        // Log access to stopped classloader
+        if (!started) {
+            try {
+                throw new IllegalStateException();
+            } catch (IllegalStateException e) {
+                log.info(sm.getString("webappClassLoader.stopped", name), e);
+            }
+        }
+
+        // (0) Check our previously loaded local class cache
+        clazz = findLoadedClass0(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.1) Check our previously loaded class cache
+        clazz = findLoadedClass(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.2) Try loading the class with the system class loader, to prevent
+        //       the webapp from overriding J2SE classes
+        try {
+            clazz = system.loadClass(name);
+            if (clazz != null) {
+                if (resolve)
+                    resolveClass(clazz);
+                return (clazz);
+            }
+        } catch (ClassNotFoundException e) {
+            // Ignore
+        }
+
+        // (0.5) Permission to access this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    securityManager.checkPackageAccess(name.substring(0,i));
+                } catch (SecurityException se) {
+                    String error = "Security Violation, attempt to use " +
+                        "Restricted Class: " + name;
+                    log.info(error, se);
+                    throw new ClassNotFoundException(error, se);
+                }
+            }
+        }
+
+        boolean delegateLoad = delegate || filter(name);
+
+        // (1) Delegate to our parent if requested
+        if (delegateLoad) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader1 " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            try {
+                clazz = Class.forName(name, false, loader);
+                if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from parent");
+                    if (resolve)
+                        resolveClass(clazz);
+                    return (clazz);
+                }
+            } catch (ClassNotFoundException e) {
+                // Ignore
+            }
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        try {
+            clazz = findClass(name);
+            if (clazz != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  Loading class from local repository");
+                if (resolve)
+                    resolveClass(clazz);
+                return (clazz);
+            }
+        } catch (ClassNotFoundException e) {
+            // Ignore
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegateLoad) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader at end: " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            try {
+                clazz = Class.forName(name, false, loader);
+                if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from parent");
+                    if (resolve)
+                        resolveClass(clazz);
+                    return (clazz);
+                }
+            } catch (ClassNotFoundException e) {
+                // Ignore
+            }
+        }
+
+        throw new ClassNotFoundException(name);
+
+    }
+
+
+    /**
+     * Get the Permissions for a CodeSource.  If this instance
+     * of WebappClassLoader is for a web application context,
+     * add read FilePermission or JndiPermissions for the base
+     * directory (if unpacked),
+     * the context URL, and jar file resources.
+     *
+     * @param codeSource where the code was loaded from
+     * @return PermissionCollection for CodeSource
+     */
+    @Override
+    protected PermissionCollection getPermissions(CodeSource codeSource) {
+
+        String codeUrl = codeSource.getLocation().toString();
+        PermissionCollection pc;
+        if ((pc = loaderPC.get(codeUrl)) == null) {
+            pc = super.getPermissions(codeSource);
+            if (pc != null) {
+                Iterator<Permission> perms = permissionList.iterator();
+                while (perms.hasNext()) {
+                    Permission p = perms.next();
+                    pc.add(p);
+                }
+                loaderPC.put(codeUrl,pc);
+            }
+        }
+        return (pc);
+
+    }
+
+
+    /**
+     * Returns the search path of URLs for loading classes and resources.
+     * This includes the original list of URLs specified to the constructor,
+     * along with any URLs subsequently appended by the addURL() method.
+     * @return the search path of URLs for loading classes and resources.
+     */
+    @Override
+    public URL[] getURLs() {
+
+        if (repositoryURLs != null) {
+            return repositoryURLs.clone();
+        }
+
+        URL[] external = super.getURLs();
+
+        int filesLength = files.length;
+        int jarFilesLength = jarRealFiles.length;
+        int externalsLength = external.length;
+        int off = 0;
+        int i;
+
+        try {
+
+            URL[] urls = new URL[filesLength + jarFilesLength + externalsLength];
+            if (searchExternalFirst) {
+                for (i = 0; i < externalsLength; i++) {
+                    urls[i] = external[i];
+                }
+                off = externalsLength;
+            }
+            for (i = 0; i < filesLength; i++) {
+                urls[off + i] = getURL(files[i], true);
+            }
+            off += filesLength;
+            for (i = 0; i < jarFilesLength; i++) {
+                urls[off + i] = getURL(jarRealFiles[i], true);
+            }
+            off += jarFilesLength;
+            if (!searchExternalFirst) {
+                for (i = 0; i < externalsLength; i++) {
+                    urls[off + i] = external[i];
+                }
+            }
+
+            repositoryURLs = urls;
+
+        } catch (MalformedURLException e) {
+            repositoryURLs = new URL[0];
+        }
+
+        return repositoryURLs.clone();
+
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Add a lifecycle event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    @Override
+    public void addLifecycleListener(LifecycleListener listener) {
+        // NOOP
+    }
+
+
+    /**
+     * Get the lifecycle listeners associated with this lifecycle. If this 
+     * Lifecycle has no listeners registered, a zero-length array is returned.
+     */
+    @Override
+    public LifecycleListener[] findLifecycleListeners() {
+        return new LifecycleListener[0];
+    }
+
+
+    /**
+     * Remove a lifecycle event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removeLifecycleListener(LifecycleListener listener) {
+        // NOOP
+    }
+
+
+    /**
+     * Obtain the current state of the source component.
+     * 
+     * @return The current state of the source component.
+     */
+    @Override
+    public LifecycleState getState() {
+        return LifecycleState.NEW;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getStateName() {
+        return getState().toString();
+    }
+
+
+    @Override
+    public void init() {
+        // NOOP
+    }
+
+    
+    /**
+     * Start the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    @Override
+    public void start() throws LifecycleException {
+
+        started = true;
+        String encoding = null;
+        try {
+            encoding = System.getProperty("file.encoding");
+        } catch (SecurityException e) {
+            return;
+        }
+        if (encoding.indexOf("EBCDIC")!=-1) {
+            needConvert = true;
+        }
+
+    }
+
+
+    public boolean isStarted() {
+        return started;
+    }
+
+    /**
+     * Stop the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    @Override
+    public void stop() throws LifecycleException {
+
+        // Clearing references should be done before setting started to
+        // false, due to possible side effects
+        clearReferences();
+        
+        started = false;
+        
+        int length = files.length;
+        for (int i = 0; i < length; i++) {
+            files[i] = null;
+        }
+
+        length = jarFiles.length;
+        for (int i = 0; i < length; i++) {
+            try {
+                if (jarFiles[i] != null) {
+                    jarFiles[i].close();
+                }
+            } catch (IOException e) {
+                // Ignore
+            }
+            jarFiles[i] = null;
+        }
+
+        notFoundResources.clear();
+        resourceEntries.clear();
+        resources = null;
+        repositories = null;
+        repositoryURLs = null;
+        files = null;
+        jarFiles = null;
+        jarRealFiles = null;
+        jarPath = null;
+        jarNames = null;
+        lastModifiedDates = null;
+        paths = null;
+        hasExternalRepositories = false;
+        parent = null;
+
+        permissionList.clear();
+        loaderPC.clear();
+
+        if (loaderDir != null) {
+            deleteDir(loaderDir);
+        }
+
+    }
+
+
+    @Override
+    public void destroy() {
+        // NOOP
+    }
+    
+
+    /**
+     * Used to periodically signal to the classloader to release 
+     * JAR resources.
+     */
+    public void closeJARs(boolean force) {
+        if (jarFiles.length > 0) {
+                synchronized (jarFiles) {
+                    if (force || (System.currentTimeMillis() 
+                                  > (lastJarAccessed + 90000))) {
+                        for (int i = 0; i < jarFiles.length; i++) {
+                            try {
+                                if (jarFiles[i] != null) {
+                                    jarFiles[i].close();
+                                    jarFiles[i] = null;
+                                }
+                            } catch (IOException e) {
+                                if (log.isDebugEnabled()) {
+                                    log.debug("Failed to close JAR", e);
+                                }
+                            }
+                        }
+                    }
+                }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+    
+    /**
+     * Clear references.
+     */
+    protected void clearReferences() {
+
+        // De-register any remaining JDBC drivers
+        clearReferencesJdbc();
+
+        // Stop any threads the web application started
+        clearReferencesThreads();
+        
+        // Check for leaks triggered by ThreadLocals loaded by this class loader
+        checkThreadLocalsForLeaks();
+        
+        // Clear RMI Targets loaded by this class loader
+        clearReferencesRmiTargets();
+
+        // Null out any static or final fields from loaded classes,
+        // as a workaround for apparent garbage collection bugs
+        if (clearReferencesStatic) {
+            clearReferencesStaticFinal();
+        }
+        
+         // Clear the IntrospectionUtils cache.
+        IntrospectionUtils.clear();
+        
+        // Clear the classloader reference in common-logging
+        if (clearReferencesLogFactoryRelease) {
+            org.apache.juli.logging.LogFactory.release(this);
+        }
+        
+        // Clear the resource bundle cache
+        // This shouldn't be necessary, the cache uses weak references but
+        // it has caused leaks. Oddly, using the leak detection code in
+        // standard host allows the class loader to be GC'd. This has been seen
+        // on Sun but not IBM JREs. Maybe a bug in Sun's GC impl?
+        clearReferencesResourceBundles();
+
+        // Clear the classloader reference in the VM's bean introspector
+        java.beans.Introspector.flushCaches();
+
+    }
+
+
+    /**
+     * Deregister any JDBC drivers registered by the webapp that the webapp
+     * forgot. This is made unnecessary complex because a) DriverManager
+     * checks the class loader of the calling class (it would be much easier
+     * if it checked the context class loader) b) using reflection would
+     * create a dependency on the DriverManager implementation which can,
+     * and has, changed.
+     * 
+     * We can't just create an instance of JdbcLeakPrevention as it will be
+     * loaded by the common class loader (since it's .class file is in the
+     * $CATALINA_HOME/lib directory). This would fail DriverManager's check
+     * on the class loader of the calling class. So, we load the bytes via
+     * our parent class loader but define the class with this class loader
+     * so the JdbcLeakPrevention looks like a webapp class to the
+     * DriverManager.
+     * 
+     * If only apps cleaned up after themselves...
+     */
+    private final void clearReferencesJdbc() {
+        InputStream is = getResourceAsStream(
+                "org/apache/catalina/loader/JdbcLeakPrevention.class");
+        // We know roughly how big the class will be (~ 1K) so allow 2k as a
+        // starting point
+        byte[] classBytes = new byte[2048];
+        int offset = 0;
+        try {
+            int read = is.read(classBytes, offset, classBytes.length-offset);
+            while (read > -1) {
+                offset += read;
+                if (offset == classBytes.length) {
+                    // Buffer full - double size
+                    byte[] tmp = new byte[classBytes.length * 2];
+                    System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
+                    classBytes = tmp;
+                }
+                read = is.read(classBytes, offset, classBytes.length-offset);
+            }
+            Class<?> lpClass =
+                defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
+                    classBytes, 0, offset, this.getClass().getProtectionDomain());
+            Object obj = lpClass.newInstance();
+            @SuppressWarnings("unchecked") // clearJdbcDriverRegistrations() returns List<String> 
+            List<String> driverNames = (List<String>) obj.getClass().getMethod(
+                    "clearJdbcDriverRegistrations").invoke(obj);
+            for (String name : driverNames) {
+                log.error(sm.getString("webappClassLoader.clearJdbc",
+                        contextName, name));
+            }
+        } catch (Exception e) {
+            // So many things to go wrong above...
+            log.warn(sm.getString(
+                    "webappClassLoader.jdbcRemoveFailed", contextName), e);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException ioe) {
+                    log.warn(sm.getString(
+                            "webappClassLoader.jdbcRemoveStreamError",
+                            contextName), ioe);
+                }
+            }
+        }
+    }
+
+
+    private final void clearReferencesStaticFinal() {
+        
+        @SuppressWarnings("unchecked") // resourceEntries is HashMap<String, ResourceEntry>
+        Collection<ResourceEntry> values =
+            ((HashMap<String,ResourceEntry>) resourceEntries.clone()).values();
+        Iterator<ResourceEntry> loadedClasses = values.iterator();
+        //
+        // walk through all loaded class to trigger initialization for
+        //    any uninitialized classes, otherwise initialization of
+        //    one class may call a previously cleared class.
+        while(loadedClasses.hasNext()) {
+            ResourceEntry entry = loadedClasses.next();
+            if (entry.loadedClass != null) {
+                Class<?> clazz = entry.loadedClass;
+                try {
+                    Field[] fields = clazz.getDeclaredFields();
+                    for (int i = 0; i < fields.length; i++) {
+                        if(Modifier.isStatic(fields[i].getModifiers())) {
+                            fields[i].get(null);
+                            break;
+                        }
+                    }
+                } catch(Throwable t) {
+                    // Ignore
+                }
+            }
+        }
+        loadedClasses = values.iterator();
+        while (loadedClasses.hasNext()) {
+            ResourceEntry entry = loadedClasses.next();
+            if (entry.loadedClass != null) {
+                Class<?> clazz = entry.loadedClass;
+                try {
+                    Field[] fields = clazz.getDeclaredFields();
+                    for (int i = 0; i < fields.length; i++) {
+                        Field field = fields[i];
+                        int mods = field.getModifiers();
+                        if (field.getType().isPrimitive() 
+                                || (field.getName().indexOf("$") != -1)) {
+                            continue;
+                        }
+                        if (Modifier.isStatic(mods)) {
+                            try {
+                                field.setAccessible(true);
+                                if (Modifier.isFinal(mods)) {
+                                    if (!((field.getType().getName().startsWith("java."))
+                                            || (field.getType().getName().startsWith("javax.")))) {
+                                        nullInstance(field.get(null));
+                                    }
+                                } else {
+                                    field.set(null, null);
+                                    if (log.isDebugEnabled()) {
+                                        log.debug("Set field " + field.getName() 
+                                                + " to null in class " + clazz.getName());
+                                    }
+                                }
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                                if (log.isDebugEnabled()) {
+                                    log.debug("Could not set field " + field.getName() 
+                                            + " to null in class " + clazz.getName(), t);
+                                }
+                            }
+                        }
+                    }
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    if (log.isDebugEnabled()) {
+                        log.debug("Could not clean fields for class " + clazz.getName(), t);
+                    }
+                }
+            }
+        }
+        
+    }
+
+
+    private void nullInstance(Object instance) {
+        if (instance == null) {
+            return;
+        }
+        Field[] fields = instance.getClass().getDeclaredFields();
+        for (int i = 0; i < fields.length; i++) {
+            Field field = fields[i];
+            int mods = field.getModifiers();
+            if (field.getType().isPrimitive() 
+                    || (field.getName().indexOf("$") != -1)) {
+                continue;
+            }
+            try {
+                field.setAccessible(true);
+                if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
+                    // Doing something recursively is too risky
+                    continue;
+                }
+                Object value = field.get(instance);
+                if (null != value) {
+                    Class<? extends Object> valueClass = value.getClass();
+                    if (!loadedByThisOrChild(valueClass)) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Not setting field " + field.getName() +
+                                    " to null in object of class " + 
+                                    instance.getClass().getName() +
+                                    " because the referenced object was of type " +
+                                    valueClass.getName() + 
+                                    " which was not loaded by this WebappClassLoader.");
+                        }
+                    } else {
+                        field.set(instance, null);
+                        if (log.isDebugEnabled()) {
+                            log.debug("Set field " + field.getName() 
+                                    + " to null in class " + instance.getClass().getName());
+                        }
+                    }
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (log.isDebugEnabled()) {
+                    log.debug("Could not set field " + field.getName() 
+                            + " to null in object instance of class " 
+                            + instance.getClass().getName(), t);
+                }
+            }
+        }
+    }
+
+
+    @SuppressWarnings("deprecation") // thread.stop()
+    private void clearReferencesThreads() {
+        Thread[] threads = getThreads();
+        
+        // Iterate over the set of threads
+        for (Thread thread : threads) {
+            if (thread != null) {
+                ClassLoader ccl = thread.getContextClassLoader();
+                if (ccl == this) {
+                    // Don't warn about this thread
+                    if (thread == Thread.currentThread()) {
+                        continue;
+                    }
+                    
+                    // Skip threads that have already died
+                    if (!thread.isAlive()) {
+                        continue;
+                    }
+
+                    // JVM controlled threads
+                    ThreadGroup tg = thread.getThreadGroup();
+                    if (tg != null &&
+                            JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
+
+                        // HttpClient keep-alive threads
+                        if (clearReferencesHttpClientKeepAliveThread &&
+                                thread.getName().equals("Keep-Alive-Timer")) {
+                            thread.setContextClassLoader(parent);
+                            log.debug(sm.getString(
+                                    "webappClassLoader.checkThreadsHttpClient"));
+                        }
+                    
+                        // Don't warn about remaining JVM controlled threads
+                        continue;
+                    }
+                   
+                    // TimerThread can be stopped safely so treat separately
+                    if (thread.getClass().getName().equals(
+                            "java.util.TimerThread") &&
+                            clearReferencesStopTimerThreads) {
+                        clearReferencesStopTimerThread(thread);
+                        continue;
+                    }
+
+                    if (isRequestThread(thread)) {
+                        log.error(sm.getString("webappClassLoader.warnRequestThread",
+                                contextName, thread.getName()));
+                    } else {
+                        log.error(sm.getString("webappClassLoader.warnThread",
+                                contextName, thread.getName()));
+                    }
+                    
+                    // Don't try an stop the threads unless explicitly
+                    // configured to do so
+                    if (!clearReferencesStopThreads) {
+                        continue;
+                    }
+                    
+                    // If the thread has been started via an executor, try
+                    // shutting down the executor
+                    try {
+                        Field targetField =
+                            thread.getClass().getDeclaredField("target");
+                        targetField.setAccessible(true);
+                        Object target = targetField.get(thread);
+                        
+                        if (target != null &&
+                                target.getClass().getCanonicalName().equals(
+                                "java.util.concurrent.ThreadPoolExecutor.Worker")) {
+                            Field executorField =
+                                target.getClass().getDeclaredField("this$0");
+                            executorField.setAccessible(true);
+                            Object executor = executorField.get(target);
+                            if (executor instanceof ThreadPoolExecutor) {
+                                ((ThreadPoolExecutor) executor).shutdownNow();
+                            }
+                        }
+                    } catch (SecurityException e) {
+                        log.warn(sm.getString(
+                                "webappClassLoader.stopThreadFail",
+                                thread.getName(), contextName), e);
+                    } catch (NoSuchFieldException e) {
+                        log.warn(sm.getString(
+                                "webappClassLoader.stopThreadFail",
+                                thread.getName(), contextName), e);
+                    } catch (IllegalArgumentException e) {
+                        log.warn(sm.getString(
+                                "webappClassLoader.stopThreadFail",
+                                thread.getName(), contextName), e);
+                    } catch (IllegalAccessException e) {
+                        log.warn(sm.getString(
+                                "webappClassLoader.stopThreadFail",
+                                thread.getName(), contextName), e);
+                    }
+
+                    // This method is deprecated and for good reason. This is
+                    // very risky code but is the only option at this point.
+                    // A *very* good reason for apps to do this clean-up
+                    // themselves.
+                    thread.stop();
+                }
+            }
+        }
+    }
+
+    
+    /*
+     * Look at a threads stack trace to see if it is a request thread or not. It
+     * isn't perfect, but it should be good-enough for most cases.
+     */
+    private boolean isRequestThread(Thread thread) {
+        
+        StackTraceElement[] elements = thread.getStackTrace();
+        
+        if (elements == null || elements.length == 0) {
+            // Must have stopped already. Too late to ignore it. Assume not a
+            // request processing thread.
+            return false;
+        }
+        
+        // Step through the methods in reverse order looking for calls to any
+        // CoyoteAdapter method. All request threads will have this unless
+        // Tomcat has been heavily modified - in which case there isn't much we
+        // can do.
+        for (int i = 0; i < elements.length; i++) {
+            StackTraceElement element = elements[elements.length - (i+1)];
+            if ("org.apache.catalina.connector.CoyoteAdapter".equals(
+                    element.getClassName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    
+    private void clearReferencesStopTimerThread(Thread thread) {
+        
+        // Need to get references to:
+        // - newTasksMayBeScheduled field
+        // - queue field
+        // - queue.clear()
+        
+        try {
+            Field newTasksMayBeScheduledField =
+                thread.getClass().getDeclaredField("newTasksMayBeScheduled");
+            newTasksMayBeScheduledField.setAccessible(true);
+            Field queueField = thread.getClass().getDeclaredField("queue");
+            queueField.setAccessible(true);
+    
+            Object queue = queueField.get(thread);
+            
+            Method clearMethod = queue.getClass().getDeclaredMethod("clear");
+            clearMethod.setAccessible(true);
+            
+            synchronized(queue) {
+                newTasksMayBeScheduledField.setBoolean(thread, false);
+                clearMethod.invoke(queue);
+                queue.notify();  // In case queue was already empty.
+            }
+            
+            log.error(sm.getString("webappClassLoader.warnTimerThread",
+                    contextName, thread.getName()));
+
+        } catch (NoSuchFieldException e) {
+            log.warn(sm.getString(
+                    "webappClassLoader.stopTimerThreadFail",
+                    thread.getName(), contextName), e);
+        } catch (IllegalAccessException e) {
+            log.warn(sm.getString(
+                    "webappClassLoader.stopTimerThreadFail",
+                    thread.getName(), contextName), e);
+        } catch (NoSuchMethodException e) {
+            log.warn(sm.getString(
+                    "webappClassLoader.stopTimerThreadFail",
+                    thread.getName(), contextName), e);
+        } catch (InvocationTargetException e) {
+            log.warn(sm.getString(
+                    "webappClassLoader.stopTimerThreadFail",
+                    thread.getName(), contextName), e);
+        }
+    }
+
+    private void checkThreadLocalsForLeaks() {
+        Thread[] threads = getThreads();
+
+        try {
+            // Make the fields in the Thread class that store ThreadLocals
+            // accessible
+            Field threadLocalsField =
+                Thread.class.getDeclaredField("threadLocals");
+            threadLocalsField.setAccessible(true);
+            Field inheritableThreadLocalsField =
+                Thread.class.getDeclaredField("inheritableThreadLocals");
+            inheritableThreadLocalsField.setAccessible(true);
+            // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
+            // accessible
+            Class<?> tlmClass =
+                Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
+            Field tableField = tlmClass.getDeclaredField("table");
+            tableField.setAccessible(true);
+            
+            for (int i = 0; i < threads.length; i++) {
+                Object threadLocalMap;
+                if (threads[i] != null) {
+                    // Clear the first map
+                    threadLocalMap = threadLocalsField.get(threads[i]);
+                    checkThreadLocalMapForLeaks(threadLocalMap, tableField);
+                    // Clear the second map
+                    threadLocalMap =
+                        inheritableThreadLocalsField.get(threads[i]);
+                    checkThreadLocalMapForLeaks(threadLocalMap, tableField);
+                }
+            }
+        } catch (SecurityException e) {
+            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
+                    contextName), e);
+        } catch (NoSuchFieldException e) {
+            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
+                    contextName), e);
+        } catch (ClassNotFoundException e) {
+            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
+                    contextName), e);
+        } catch (IllegalArgumentException e) {
+            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
+                    contextName), e);
+        } catch (IllegalAccessException e) {
+            log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail",
+                    contextName), e);
+        }       
+    }
+
+
+    /**
+     * Analyzes the given thread local map object. Also pass in the field that
+     * points to the internal table to save re-calculating it on every
+     * call to this method.
+     */
+    private void checkThreadLocalMapForLeaks(Object map,
+            Field internalTableField) throws IllegalAccessException,
+            NoSuchFieldException {
+        if (map != null) {
+            Object[] table = (Object[]) internalTableField.get(map);
+            if (table != null) {
+                for (int j =0; j < table.length; j++) {
+                    if (table[j] != null) {
+                        boolean potentialLeak = false;
+                        // Check the key
+                        Object key = ((Reference<?>) table[j]).get();
+                        if (this.equals(key) || loadedByThisOrChild(key)) {
+                            potentialLeak = true;
+                        }
+                        // Check the value
+                        Field valueField =
+                            table[j].getClass().getDeclaredField("value");
+                        valueField.setAccessible(true);
+                        Object value = valueField.get(table[j]);
+                        if (this.equals(value) || loadedByThisOrChild(value)) {
+                            potentialLeak = true;
+                        }
+                        if (potentialLeak) {
+                            Object[] args = new Object[5];
+                            args[0] = contextName;
+                            if (key != null) {
+                                args[1] = getPrettyClassName(key.getClass());
+                                try {
+                                    args[2] = key.toString();
+                                } catch (Exception e) {
+                                    log.error(sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaks.badKey",
+                                            args[1]), e);
+                                    args[2] = sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaks.unknown");
+                                }
+                            }
+                            if (value != null) {
+                                args[3] = getPrettyClassName(value.getClass());
+                                try {
+                                    args[4] = value.toString();
+                                } catch (Exception e) {
+                                    log.error(sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaks.badValue",
+                                            args[3]), e);
+                                    args[4] = sm.getString(
+                                    "webappClassLoader.checkThreadLocalsForLeaks.unknown");
+                                }
+                            }
+                            if (value == null) {
+                                if (log.isDebugEnabled()) {
+                                    log.debug(sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaksDebug",
+                                            args));
+                                }
+                            } else {
+                                log.error(sm.getString(
+                                        "webappClassLoader.checkThreadLocalsForLeaks",
+                                        args));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private String getPrettyClassName(Class<?> clazz) {
+        String name = clazz.getCanonicalName();
+        if (name==null){
+            name = clazz.getName();
+        }
+        return name;
+    }
+    
+    /**
+     * @param o object to test, may be null
+     * @return <code>true</code> if o has been loaded by the current classloader
+     * or one of its descendants.
+     */
+    private boolean loadedByThisOrChild(Object o) {
+        if (o == null) {
+            return false;
+        }
+
+        Class<?> clazz;
+        if (o instanceof Class) {
+            clazz = (Class<?>) o;
+        } else {
+            clazz = o.getClass();
+        }
+
+        ClassLoader cl = clazz.getClassLoader();
+        while (cl != null) {
+            if(cl == this) {
+                return true;
+            }
+            cl = cl.getParent();
+        }
+        return false;
+    }
+        
+    /*
+     * Get the set of current threads as an array.
+     */
+    private Thread[] getThreads() {
+        // Get the current thread group 
+        ThreadGroup tg = Thread.currentThread( ).getThreadGroup( );
+        // Find the root thread group
+        while (tg.getParent() != null) {
+            tg = tg.getParent();
+        }
+        
+        int threadCountGuess = tg.activeCount() + 50;
+        Thread[] threads = new Thread[threadCountGuess];
+        int threadCountActual = tg.enumerate(threads);
+        // Make sure we don't miss any threads
+        while (threadCountActual == threadCountGuess) {
+            threadCountGuess *=2;
+            threads = new Thread[threadCountGuess];
+            // Note tg.enumerate(Thread[]) silently ignores any threads that
+            // can't fit into the array 
+            threadCountActual = tg.enumerate(threads);
+        }
+        
+        return threads;
+    }
+
+
+    /**
+     * This depends on the internals of the Sun JVM so it does everything by
+     * reflection.
+     */
+    private void clearReferencesRmiTargets() {
+        try {
+            // Need access to the ccl field of sun.rmi.transport.Target
+            Class<?> objectTargetClass =
+                Class.forName("sun.rmi.transport.Target");
+            Field cclField = objectTargetClass.getDeclaredField("ccl");
+            cclField.setAccessible(true);
+
+            // Clear the objTable map
+            Class<?> objectTableClass =
+                Class.forName("sun.rmi.transport.ObjectTable");
+            Field objTableField = objectTableClass.getDeclaredField("objTable");
+            objTableField.setAccessible(true);
+            Object objTable = objTableField.get(null);
+            if (objTable == null) {
+                return;
+            }
+            
+            // Iterate over the values in the table
+            if (objTable instanceof Map<?,?>) {
+                Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
+                while (iter.hasNext()) {
+                    Object obj = iter.next();
+                    Object cclObject = cclField.get(obj);
+                    if (this == cclObject) {
+                        iter.remove();
+                    }
+                }
+            }
+
+            // Clear the implTable map
+            Field implTableField = objectTableClass.getDeclaredField("implTable");
+            implTableField.setAccessible(true);
+            Object implTable = implTableField.get(null);
+            if (implTable == null) {
+                return;
+            }
+            
+            // Iterate over the values in the table
+            if (implTable instanceof Map<?,?>) {
+                Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
+                while (iter.hasNext()) {
+                    Object obj = iter.next();
+                    Object cclObject = cclField.get(obj);
+                    if (this == cclObject) {
+                        iter.remove();
+                    }
+                }
+            }
+        } catch (ClassNotFoundException e) {
+            log.info(sm.getString("webappClassLoader.clearRmiInfo",
+                    contextName), e);
+        } catch (SecurityException e) {
+            log.warn(sm.getString("webappClassLoader.clearRmiFail",
+                    contextName), e);
+        } catch (NoSuchFieldException e) {
+            log.warn(sm.getString("webappClassLoader.clearRmiFail",
+                    contextName), e);
+        } catch (IllegalArgumentException e) {
+            log.warn(sm.getString("webappClassLoader.clearRmiFail",
+                    contextName), e);
+        } catch (IllegalAccessException e) {
+            log.warn(sm.getString("webappClassLoader.clearRmiFail",
+                    contextName), e);
+        }
+    }
+    
+    
+    /**
+     * Clear the {@link ResourceBundle} cache of any bundles loaded by this
+     * class loader or any class loader where this loader is a parent class
+     * loader. Whilst {@link ResourceBundle#clearCache()} could be used there
+     * are complications around the
+     * {@link org.apache.jasper.servlet.JasperLoader} that mean a reflection
+     * based approach is more likely to be complete.
+     * 
+     * The ResourceBundle is using WeakReferences so it shouldn't be pinning the
+     * class loader in memory. However, it is. Therefore clear ou the
+     * references.
+     */
+    private void clearReferencesResourceBundles() {
+        // Get a reference to the cache
+        try {
+            Field cacheListField =
+                ResourceBundle.class.getDeclaredField("cacheList");
+            cacheListField.setAccessible(true);
+
+            // Java 6 uses ConcurrentMap
+            // Java 5 uses SoftCache extends Abstract Map
+            // So use Map and it *should* work with both
+            Map<?,?> cacheList = (Map<?,?>) cacheListField.get(null);
+            
+            // Get the keys (loader references are in the key)
+            Set<?> keys = cacheList.keySet();
+            
+            Field loaderRefField = null;
+            
+            // Iterate over the keys looking at the loader instances
+            Iterator<?> keysIter = keys.iterator();
+            
+            int countRemoved = 0;
+            
+            while (keysIter.hasNext()) {
+                Object key = keysIter.next();
+                
+                if (loaderRefField == null) {
+                    loaderRefField =
+                        key.getClass().getDeclaredField("loaderRef");
+                    loaderRefField.setAccessible(true);
+                }
+                WeakReference<?> loaderRef =
+                    (WeakReference<?>) loaderRefField.get(key);
+                
+                ClassLoader loader = (ClassLoader) loaderRef.get();
+                
+                while (loader != null && loader != this) {
+                    loader = loader.getParent();
+                }
+                
+                if (loader != null) {
+                    keysIter.remove();
+                    countRemoved++;
+                }
+            }
+            
+            if (countRemoved > 0 && log.isDebugEnabled()) {
+                log.debug(sm.getString(
+                        "webappClassLoader.clearReferencesResourceBundlesCount",
+                        Integer.valueOf(countRemoved), contextName));
+            }
+        } catch (SecurityException e) {
+            log.error(sm.getString(
+                    "webappClassLoader.clearReferencesResourceBundlesFail",
+                    contextName), e);
+        } catch (NoSuchFieldException e) {
+            if (System.getProperty("java.vendor").startsWith("Sun")) {
+                log.error(sm.getString(
+                "webappClassLoader.clearReferencesResourceBundlesFail",
+                contextName), e);
+            } else {
+                log.debug(sm.getString(
+                "webappClassLoader.clearReferencesResourceBundlesFail",
+                contextName), e);
+            }
+        } catch (IllegalArgumentException e) {
+            log.error(sm.getString(
+                    "webappClassLoader.clearReferencesResourceBundlesFail",
+                    contextName), e);
+        } catch (IllegalAccessException e) {
+            log.error(sm.getString(
+                    "webappClassLoader.clearReferencesResourceBundlesFail",
+                    contextName), e);
+        }
+    }
+
+
+    /**
+     * Used to periodically signal to the classloader to release JAR resources.
+     */
+    protected boolean openJARs() {
+        if (started && (jarFiles.length > 0)) {
+            lastJarAccessed = System.currentTimeMillis();
+            if (jarFiles[0] == null) {
+                for (int i = 0; i < jarFiles.length; i++) {
+                    try {
+                        jarFiles[i] = new JarFile(jarRealFiles[i]);
+                    } catch (IOException e) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Failed to open JAR", e);
+                        }
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Find specified class in local repositories.
+     *
+     * @return the loaded class, or null if the class isn't found
+     */
+    protected Class<?> findClassInternal(String name)
+        throws ClassNotFoundException {
+
+        if (!validate(name))
+            throw new ClassNotFoundException(name);
+
+        String tempPath = name.replace('.', '/');
+        String classPath = tempPath + ".class";
+
+        ResourceEntry entry = null;
+
+        if (securityManager != null) {
+            PrivilegedAction<ResourceEntry> dp =
+                new PrivilegedFindResourceByName(name, classPath);
+            entry = AccessController.doPrivileged(dp);
+        } else {
+            entry = findResourceInternal(name, classPath);
+        }
+
+        if (entry == null)
+            throw new ClassNotFoundException(name);
+
+        Class<?> clazz = entry.loadedClass;
+        if (clazz != null)
+            return clazz;
+
+        synchronized (this) {
+            clazz = entry.loadedClass;
+            if (clazz != null)
+                return clazz;
+
+            if (entry.binaryContent == null)
+                throw new ClassNotFoundException(name);
+
+            // Looking up the package
+            String packageName = null;
+            int pos = name.lastIndexOf('.');
+            if (pos != -1)
+                packageName = name.substring(0, pos);
+        
+            Package pkg = null;
+        
+            if (packageName != null) {
+                pkg = getPackage(packageName);
+                // Define the package (if null)
+                if (pkg == null) {
+                    try {
+                        if (entry.manifest == null) {
+                            definePackage(packageName, null, null, null, null,
+                                    null, null, null);
+                        } else {
+                            definePackage(packageName, entry.manifest,
+                                    entry.codeBase);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        // Ignore: normal error due to dual definition of package
+                    }
+                    pkg = getPackage(packageName);
+                }
+            }
+    
+            if (securityManager != null) {
+
+                // Checking sealing
+                if (pkg != null) {
+                    boolean sealCheck = true;
+                    if (pkg.isSealed()) {
+                        sealCheck = pkg.isSealed(entry.codeBase);
+                    } else {
+                        sealCheck = (entry.manifest == null)
+                            || !isPackageSealed(packageName, entry.manifest);
+                    }
+                    if (!sealCheck)
+                        throw new SecurityException
+                            ("Sealing violation loading " + name + " : Package "
+                             + packageName + " is sealed.");
+                }
+    
+            }
+
+            try {
+                clazz = defineClass(name, entry.binaryContent, 0,
+                        entry.binaryContent.length, 
+                        new CodeSource(entry.codeBase, entry.certificates));
+            } catch (UnsupportedClassVersionError ucve) {
+                throw new UnsupportedClassVersionError(
+                        ucve.getLocalizedMessage() + " " +
+                        sm.getString("webappClassLoader.wrongVersion",
+                                name));
+            }
+            entry.loadedClass = clazz;
+            entry.binaryContent = null;
+            entry.source = null;
+            entry.codeBase = null;
+            entry.manifest = null;
+            entry.certificates = null;
+        }
+        
+        return clazz;
+
+    }
+
+    /**
+     * Find specified resource in local repositories.
+     *
+     * @return the loaded resource, or null if the resource isn't found
+     */
+    protected ResourceEntry findResourceInternal(File file, String path){
+        ResourceEntry entry = new ResourceEntry();
+        try {
+            entry.source = getURI(new File(file, path));
+            entry.codeBase = getURL(new File(file, path), false);
+        } catch (MalformedURLException e) {
+            return null;
+        }   
+        return entry;
+    }
+    
+
+    /**
+     * Find specified resource in local repositories.
+     *
+     * @return the loaded resource, or null if the resource isn't found
+     */
+    protected ResourceEntry findResourceInternal(String name, String path) {
+
+        if (!started) {
+            log.info(sm.getString("webappClassLoader.stopped", name));
+            return null;
+        }
+
+        if ((name == null) || (path == null))
+            return null;
+
+        ResourceEntry entry = resourceEntries.get(name);
+        if (entry != null)
+            return entry;
+
+        int contentLength = -1;
+        InputStream binaryStream = null;
+
+        int jarFilesLength = jarFiles.length;
+        int repositoriesLength = repositories.length;
+
+        int i;
+
+        Resource resource = null;
+
+        boolean fileNeedConvert = false;
+
+        for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
+            try {
+
+                String fullPath = repositories[i] + path;
+
+                Object lookupResult = resources.lookup(fullPath);
+                if (lookupResult instanceof Resource) {
+                    resource = (Resource) lookupResult;
+                }
+
+                // Note : Not getting an exception here means the resource was
+                // found
+                entry = findResourceInternal(files[i], path);
+
+                ResourceAttributes attributes =
+                    (ResourceAttributes) resources.getAttributes(fullPath);
+                contentLength = (int) attributes.getContentLength();
+                entry.lastModified = attributes.getLastModified();
+
+                if (resource != null) {
+
+
+                    try {
+                        binaryStream = resource.streamContent();
+                    } catch (IOException e) {
+                        return null;
+                    }
+
+                    if (needConvert) {
+                        if (path.endsWith(".properties")) {
+                            fileNeedConvert = true;
+                        }
+                    }
+
+                    // Register the full path for modification checking
+                    // Note: Only syncing on a 'constant' object is needed
+                    synchronized (allPermission) {
+
+                        int j;
+
+                        long[] result2 = 
+                            new long[lastModifiedDates.length + 1];
+                        for (j = 0; j < lastModifiedDates.length; j++) {
+                            result2[j] = lastModifiedDates[j];
+                        }
+                        result2[lastModifiedDates.length] = entry.lastModified;
+                        lastModifiedDates = result2;
+
+                        String[] result = new String[paths.length + 1];
+                        for (j = 0; j < paths.length; j++) {
+                            result[j] = paths[j];
+                        }
+                        result[paths.length] = fullPath;
+                        paths = result;
+
+                    }
+
+                }
+
+            } catch (NamingException e) {
+                // Ignore
+            }
+        }
+
+        if ((entry == null) && (notFoundResources.containsKey(name)))
+            return null;
+
+        JarEntry jarEntry = null;
+
+        synchronized (jarFiles) {
+
+            try {
+                if (!openJARs()) {
+                    return null;
+                }
+                for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
+    
+                    jarEntry = jarFiles[i].getJarEntry(path);
+    
+                    if (jarEntry != null) {
+    
+                        entry = new ResourceEntry();
+                        try {
+                            entry.codeBase = getURL(jarRealFiles[i], false);
+                            String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+                            jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
+                            entry.source = new URL(jarFakeUrl);
+                            entry.lastModified = jarRealFiles[i].lastModified();
+                        } catch (MalformedURLException e) {
+                            return null;
+                        }
+                        contentLength = (int) jarEntry.getSize();
+                        try {
+                            entry.manifest = jarFiles[i].getManifest();
+                            binaryStream = jarFiles[i].getInputStream(jarEntry);
+                        } catch (IOException e) {
+                            return null;
+                        }
+    
+                        // Extract resources contained in JAR to the workdir
+                        if (antiJARLocking && !(path.endsWith(".class"))) {
+                            byte[] buf = new byte[1024];
+                            File resourceFile = new File
+                                (loaderDir, jarEntry.getName());
+                            if (!resourceFile.exists()) {
+                                Enumeration<JarEntry> entries =
+                                    jarFiles[i].entries();
+                                while (entries.hasMoreElements()) {
+                                    JarEntry jarEntry2 =  entries.nextElement();
+                                    if (!(jarEntry2.isDirectory()) 
+                                        && (!jarEntry2.getName().endsWith
+                                            (".class"))) {
+                                        resourceFile = new File
+                                            (loaderDir, jarEntry2.getName());
+                                        try {
+                                            if (!resourceFile.getCanonicalPath().startsWith(
+                                                    canonicalLoaderDir)) {
+                                                throw new IllegalArgumentException(
+                                                        sm.getString("webappClassLoader.illegalJarPath",
+                                                    jarEntry2.getName()));
+                                            }
+                                        } catch (IOException ioe) {
+                                            throw new IllegalArgumentException(
+                                                    sm.getString("webappClassLoader.validationErrorJarPath",
+                                                            jarEntry2.getName()), ioe);
+                                        }                                 
+                                        resourceFile.getParentFile().mkdirs();
+                                        FileOutputStream os = null;
+                                        InputStream is = null;
+                                        try {
+                                            is = jarFiles[i].getInputStream
+                                                (jarEntry2);
+                                            os = new FileOutputStream
+                                                (resourceFile);
+                                            while (true) {
+                                                int n = is.read(buf);
+                                                if (n <= 0) {
+                                                    break;
+                                                }
+                                                os.write(buf, 0, n);
+                                            }
+                                        } catch (IOException e) {
+                                            // Ignore
+                                        } finally {
+                                            try {
+                                                if (is != null) {
+                                                    is.close();
+                                                }
+                                            } catch (IOException e) {
+                                                // Ignore
+                                            }
+                                            try {
+                                                if (os != null) {
+                                                    os.close();
+                                                }
+                                            } catch (IOException e) {
+                                                // Ignore
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+    
+                    }
+    
+                }
+    
+                if (entry == null) {
+                    synchronized (notFoundResources) {
+                        notFoundResources.put(name, name);
+                    }
+                    return null;
+                }
+    
+                if (binaryStream != null) {
+    
+                    byte[] binaryContent = new byte[contentLength];
+    
+                    int pos = 0;
+                    try {
+    
+                        while (true) {
+                            int n = binaryStream.read(binaryContent, pos,
+                                                      binaryContent.length - pos);
+                            if (n <= 0)
+                                break;
+                            pos += n;
+                        }
+                    } catch (IOException e) {
+                        log.error(sm.getString("webappClassLoader.readError", name), e);
+                        return null;
+                    }
+                    if (fileNeedConvert) {
+                        // Workaround for certain files on platforms that use
+                        // EBCDIC encoding, when they are read through FileInputStream.
+                        // See commit message of rev.303915 for details
+                        // http://svn.apache.org/viewvc?view=revision&revision=303915
+                        String str = new String(binaryContent,0,pos);
+                        try {
+                            binaryContent = str.getBytes("UTF-8");
+                        } catch (Exception e) {
+                            return null;
+                        }
+                    }
+                    entry.binaryContent = binaryContent;
+    
+                    // The certificates are only available after the JarEntry 
+                    // associated input stream has been fully read
+                    if (jarEntry != null) {
+                        entry.certificates = jarEntry.getCertificates();
+                    }
+    
+                }
+            } finally {
+                if (binaryStream != null) {
+                    try {
+                        binaryStream.close();
+                    } catch (IOException e) { /* Ignore */}
+                }
+            }
+        }
+
+        // Add the entry in the local resource repository
+        synchronized (resourceEntries) {
+            // Ensures that all the threads which may be in a race to load
+            // a particular class all end up with the same ResourceEntry
+            // instance
+            ResourceEntry entry2 = resourceEntries.get(name);
+            if (entry2 == null) {
+                resourceEntries.put(name, entry);
+            } else {
+                entry = entry2;
+            }
+        }
+
+        return entry;
+
+    }
+
+
+    /**
+     * Returns true if the specified package name is sealed according to the
+     * given manifest.
+     */
+    protected boolean isPackageSealed(String name, Manifest man) {
+
+        String path = name.replace('.', '/') + '/';
+        Attributes attr = man.getAttributes(path); 
+        String sealed = null;
+        if (attr != null) {
+            sealed = attr.getValue(Name.SEALED);
+        }
+        if (sealed == null) {
+            if ((attr = man.getMainAttributes()) != null) {
+                sealed = attr.getValue(Name.SEALED);
+            }
+        }
+        return "true".equalsIgnoreCase(sealed);
+
+    }
+
+
+    /**
+     * Finds the resource with the given name if it has previously been
+     * loaded and cached by this class loader, and return an input stream
+     * to the resource data.  If this resource has not been cached, return
+     * <code>null</code>.
+     *
+     * @param name Name of the resource to return
+     */
+    protected InputStream findLoadedResource(String name) {
+
+        ResourceEntry entry = resourceEntries.get(name);
+        if (entry != null) {
+            if (entry.binaryContent != null)
+                return new ByteArrayInputStream(entry.binaryContent);
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Finds the class with the given name if it has previously been
+     * loaded and cached by this class loader, and return the Class object.
+     * If this class has not been cached, return <code>null</code>.
+     *
+     * @param name Name of the resource to return
+     */
+    protected Class<?> findLoadedClass0(String name) {
+
+        ResourceEntry entry = resourceEntries.get(name);
+        if (entry != null) {
+            return entry.loadedClass;
+        }
+        return (null);  // FIXME - findLoadedResource()
+
+    }
+
+
+    /**
+     * Refresh the system policy file, to pick up eventual changes.
+     */
+    protected void refreshPolicy() {
+
+        try {
+            // The policy file may have been modified to adjust 
+            // permissions, so we're reloading it when loading or 
+            // reloading a Context
+            Policy policy = Policy.getPolicy();
+            policy.refresh();
+        } catch (AccessControlException e) {
+            // Some policy files may restrict this, even for the core,
+            // so this exception is ignored
+        }
+
+    }
+
+
+    /**
+     * Filter classes.
+     * 
+     * @param name class name
+     * @return true if the class should be filtered
+     */
+    protected boolean filter(String name) {
+
+        if (name == null)
+            return false;
+
+        // Looking up the package
+        String packageName = null;
+        int pos = name.lastIndexOf('.');
+        if (pos != -1)
+            packageName = name.substring(0, pos);
+        else
+            return false;
+
+        for (int i = 0; i < packageTriggers.length; i++) {
+            if (packageName.startsWith(packageTriggers[i]))
+                return true;
+        }
+
+        return false;
+
+    }
+
+
+    /**
+     * Validate a classname. As per SRV.9.7.2, we must restrict loading of 
+     * classes from J2SE (java.*) and most classes of the servlet API 
+     * (javax.servlet.*). That should enhance robustness and prevent a number
+     * of user error (where an older version of servlet.jar would be present
+     * in /WEB-INF/lib).
+     * 
+     * @param name class name
+     * @return true if the name is valid
+     */
+    protected boolean validate(String name) {
+
+        // Need to be careful with order here
+        if (name == null) {
+            // Can't load a class without a name
+            return false;
+        }
+        if (name.startsWith("java.")) {
+            // Must never load java.* classes
+            return false;
+        }
+        if (name.startsWith("javax.servlet.jsp.jstl")) {
+            // OK for web apps to package JSTL
+            return true;
+        }
+        if (name.startsWith("javax.servlet.")) {
+            // Web apps should never package any other Servlet or JSP classes
+            return false;
+        }
+
+        // Assume everything else is OK
+        return true;
+
+    }
+
+
+    /**
+     * Check the specified JAR file, and return <code>true</code> if it does
+     * not contain any of the trigger classes.
+     *
+     * @param jarfile The JAR file to be checked
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    protected boolean validateJarFile(File jarfile)
+        throws IOException {
+
+        if (triggers == null)
+            return (true);
+        JarFile jarFile = new JarFile(jarfile);
+        for (int i = 0; i < triggers.length; i++) {
+            Class<?> clazz = null;
+            try {
+                if (parent != null) {
+                    clazz = parent.loadClass(triggers[i]);
+                } else {
+                    clazz = Class.forName(triggers[i]);
+                }
+            } catch (Exception e) {
+                clazz = null;
+            }
+            if (clazz == null)
+                continue;
+            String name = triggers[i].replace('.', '/') + ".class";
+            if (log.isDebugEnabled())
+                log.debug(" Checking for " + name);
+            JarEntry jarEntry = jarFile.getJarEntry(name);
+            if (jarEntry != null) {
+                log.info("validateJarFile(" + jarfile + 
+                    ") - jar not loaded. See Servlet Spec 2.3, "
+                    + "section 9.7.2. Offending class: " + name);
+                jarFile.close();
+                return (false);
+            }
+        }
+        jarFile.close();
+        return (true);
+
+    }
+
+
+    /**
+     * Get URL.
+     */
+    protected URL getURL(File file, boolean encoded)
+        throws MalformedURLException {
+
+        File realFile = file;
+        try {
+            realFile = realFile.getCanonicalFile();
+        } catch (IOException e) {
+            // Ignore
+        }
+        if(encoded) {
+            return getURI(realFile);
+        }
+        
+        return realFile.toURI().toURL();
+    }
+
+
+    /**
+     * Get URL.
+     */
+    protected URL getURI(File file)
+        throws MalformedURLException {
+
+
+        File realFile = file;
+        try {
+            realFile = realFile.getCanonicalFile();
+        } catch (IOException e) {
+            // Ignore
+        }
+        return realFile.toURI().toURL();
+
+    }
+
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * subdirectories recursively.
+     *
+     * @param dir File object representing the directory to be deleted
+     */
+    protected static void deleteDir(File dir) {
+
+        String files[] = dir.list();
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; i < files.length; i++) {
+            File file = new File(dir, files[i]);
+            if (file.isDirectory()) {
+                deleteDir(file);
+            } else {
+                file.delete();
+            }
+        }
+        dir.delete();
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/WebappLoader.java b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/WebappLoader.java
new file mode 100644
index 0000000..268820d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/WebappLoader.java
@@ -0,0 +1,1210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLStreamHandlerFactory;
+import java.util.ArrayList;
+import java.util.jar.JarFile;
+
+import javax.management.ObjectName;
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.servlet.ServletContext;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Loader;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.naming.resources.DirContextURLStreamHandler;
+import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
+import org.apache.naming.resources.Resource;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Classloader implementation which is specialized for handling web
+ * applications in the most efficient way, while being Catalina aware (all
+ * accesses to resources are made through the DirContext interface).
+ * This class loader supports detection of modified
+ * Java classes, which can be used to implement auto-reload support.
+ * <p>
+ * This class loader is configured by adding the pathnames of directories,
+ * JAR files, and ZIP files with the <code>addRepository()</code> method,
+ * prior to calling <code>start()</code>.  When a new class is required,
+ * these repositories will be consulted first to locate the class.  If it
+ * is not present, the system class loader will be used instead.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: WebappLoader.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+
+public class WebappLoader extends LifecycleMBeanBase
+    implements Loader, PropertyChangeListener {
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new WebappLoader with no defined parent class loader
+     * (so that the actual parent will be the system class loader).
+     */
+    public WebappLoader() {
+
+        this(null);
+
+    }
+
+
+    /**
+     * Construct a new WebappLoader with the specified class loader
+     * to be defined as the parent of the ClassLoader we ultimately create.
+     *
+     * @param parent The parent class loader
+     */
+    public WebappLoader(ClassLoader parent) {
+        super();
+        this.parentClassLoader = parent;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * First load of the class.
+     */
+    private static boolean first = true;
+
+
+    /**
+     * The class loader being managed by this Loader component.
+     */
+    private WebappClassLoader classLoader = null;
+
+
+    /**
+     * The Container with which this Loader has been associated.
+     */
+    private Container container = null;
+
+
+    /**
+     * The "follow standard delegation model" flag that will be used to
+     * configure our ClassLoader.
+     */
+    private boolean delegate = false;
+
+
+    /**
+     * The descriptive information about this Loader implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.loader.WebappLoader/1.0";
+
+
+    /**
+     * The Java class name of the ClassLoader implementation to be used.
+     * This class should extend WebappClassLoader, otherwise, a different 
+     * loader implementation must be used.
+     */
+    private String loaderClass =
+        "org.apache.catalina.loader.WebappClassLoader";
+
+
+    /**
+     * The parent class loader of the class loader we will create.
+     */
+    private ClassLoader parentClassLoader = null;
+
+
+    /**
+     * The reloadable flag for this Loader.
+     */
+    private boolean reloadable = false;
+
+
+    /**
+     * The set of repositories associated with this class loader.
+     */
+    private String repositories[] = new String[0];
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The property change support for this component.
+     */
+    protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+
+    /**
+     * Classpath set in the loader.
+     */
+    private String classpath = null;
+
+
+    /**
+     * Repositories that are set in the loader, for JMX.
+     */
+    private ArrayList<String> loaderRepositories = null;
+
+
+    /**
+     * Whether we should search the external repositories first
+     */
+    private boolean searchExternalFirst = false;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Java class loader to be used by this Container.
+     */
+    @Override
+    public ClassLoader getClassLoader() {
+
+        return classLoader;
+
+    }
+
+
+    /**
+     * Return the Container with which this Logger has been associated.
+     */
+    @Override
+    public Container getContainer() {
+
+        return (container);
+
+    }
+
+
+    /**
+     * Set the Container with which this Logger has been associated.
+     *
+     * @param container The associated Container
+     */
+    @Override
+    public void setContainer(Container container) {
+
+        // Deregister from the old Container (if any)
+        if ((this.container != null) && (this.container instanceof Context))
+            ((Context) this.container).removePropertyChangeListener(this);
+
+        // Process this property change
+        Container oldContainer = this.container;
+        this.container = container;
+        support.firePropertyChange("container", oldContainer, this.container);
+
+        // Register with the new Container (if any)
+        if ((this.container != null) && (this.container instanceof Context)) {
+            setReloadable( ((Context) this.container).getReloadable() );
+            ((Context) this.container).addPropertyChangeListener(this);
+        }
+
+    }
+
+
+    /**
+     * Return the "follow standard delegation model" flag used to configure
+     * our ClassLoader.
+     */
+    @Override
+    public boolean getDelegate() {
+
+        return (this.delegate);
+
+    }
+
+
+    /**
+     * Set the "follow standard delegation model" flag used to configure
+     * our ClassLoader.
+     *
+     * @param delegate The new flag
+     */
+    @Override
+    public void setDelegate(boolean delegate) {
+
+        boolean oldDelegate = this.delegate;
+        this.delegate = delegate;
+        support.firePropertyChange("delegate", Boolean.valueOf(oldDelegate),
+                                   Boolean.valueOf(this.delegate));
+
+    }
+
+
+    /**
+     * Return descriptive information about this Loader implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Return the ClassLoader class name.
+     */
+    public String getLoaderClass() {
+
+        return (this.loaderClass);
+
+    }
+
+
+    /**
+     * Set the ClassLoader class name.
+     *
+     * @param loaderClass The new ClassLoader class name
+     */
+    public void setLoaderClass(String loaderClass) {
+
+        this.loaderClass = loaderClass;
+
+    }
+
+
+    /**
+     * Return the reloadable flag for this Loader.
+     */
+    @Override
+    public boolean getReloadable() {
+
+        return (this.reloadable);
+
+    }
+
+
+    /**
+     * Set the reloadable flag for this Loader.
+     *
+     * @param reloadable The new reloadable flag
+     */
+    @Override
+    public void setReloadable(boolean reloadable) {
+
+        // Process this property change
+        boolean oldReloadable = this.reloadable;
+        this.reloadable = reloadable;
+        support.firePropertyChange("reloadable",
+                                   Boolean.valueOf(oldReloadable),
+                                   Boolean.valueOf(this.reloadable));
+
+    }
+
+    /**
+     * @return Returns searchExternalFirst.
+     */
+    public boolean getSearchExternalFirst() {
+        return searchExternalFirst;
+    }
+
+    /**
+     * @param searchExternalFirst Whether external repositories should be searched first
+     */
+    public void setSearchExternalFirst(boolean searchExternalFirst) {
+        this.searchExternalFirst = searchExternalFirst;
+        if (classLoader != null) {
+            classLoader.setSearchExternalFirst(searchExternalFirst);
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    @Override
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+        support.addPropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Add a new repository to the set of repositories for this class loader.
+     *
+     * @param repository Repository to be added
+     */
+    @Override
+    public void addRepository(String repository) {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("webappLoader.addRepository", repository));
+
+        for (int i = 0; i < repositories.length; i++) {
+            if (repository.equals(repositories[i]))
+                return;
+        }
+        String results[] = new String[repositories.length + 1];
+        for (int i = 0; i < repositories.length; i++)
+            results[i] = repositories[i];
+        results[repositories.length] = repository;
+        repositories = results;
+
+        if (getState().isAvailable() && (classLoader != null)) {
+            classLoader.addRepository(repository);
+            if( loaderRepositories != null ) loaderRepositories.add(repository);
+            setClassPath();
+        }
+
+    }
+
+
+    /**
+     * Execute a periodic task, such as reloading, etc. This method will be
+     * invoked inside the classloading context of this container. Unexpected
+     * throwables will be caught and logged.
+     */
+    @Override
+    public void backgroundProcess() {
+        if (reloadable && modified()) {
+            try {
+                Thread.currentThread().setContextClassLoader
+                    (WebappLoader.class.getClassLoader());
+                if (container instanceof StandardContext) {
+                    ((StandardContext) container).reload();
+                }
+            } finally {
+                if (container.getLoader() != null) {
+                    Thread.currentThread().setContextClassLoader
+                        (container.getLoader().getClassLoader());
+                }
+            }
+        } else {
+            closeJARs(false);
+        }
+    }
+
+
+    /**
+     * Return the set of repositories defined for this class loader.
+     * If none are defined, a zero-length array is returned.
+     * For security reason, returns a clone of the Array (since 
+     * String are immutable).
+     */
+    @Override
+    public String[] findRepositories() {
+
+        return repositories.clone();
+
+    }
+
+    public String[] getRepositories() {
+        return repositories.clone();
+    }
+
+    /** Extra repositories for this loader
+     */
+    public String getRepositoriesString() {
+        StringBuilder sb=new StringBuilder();
+        for( int i=0; i<repositories.length ; i++ ) {
+            sb.append( repositories[i]).append(":");
+        }
+        return sb.toString();
+    }
+
+    public String[] getLoaderRepositories() {
+        if( loaderRepositories==null ) return  null;
+        String res[]=new String[ loaderRepositories.size()];
+        loaderRepositories.toArray(res);
+        return res;
+    }
+
+    public String getLoaderRepositoriesString() {
+        String repositories[]=getLoaderRepositories();
+        StringBuilder sb=new StringBuilder();
+        for( int i=0; i<repositories.length ; i++ ) {
+            sb.append( repositories[i]).append(":");
+        }
+        return sb.toString();
+    }
+
+
+    /** 
+     * Classpath, as set in org.apache.catalina.jsp_classpath context
+     * property
+     *
+     * @return The classpath
+     */
+    public String getClasspath() {
+        return classpath;
+    }
+
+
+    /**
+     * Has the internal repository associated with this Loader been modified,
+     * such that the loaded classes should be reloaded?
+     */
+    @Override
+    public boolean modified() {
+        return classLoader != null ? classLoader.modified() : false ;
+    }
+
+
+    /**
+     * Used to periodically signal to the classloader to release JAR resources.
+     */
+    public void closeJARs(boolean force) {
+        if (classLoader !=null) {
+            classLoader.closeJARs(force);
+        }
+    }
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+        support.removePropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("WebappLoader[");
+        if (container != null)
+            sb.append(container.getName());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    /**
+     * Start associated {@link ClassLoader} and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected void startInternal() throws LifecycleException {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("webappLoader.starting"));
+
+        if (container.getResources() == null) {
+            log.info("No resources for " + container);
+            setState(LifecycleState.STARTING);
+            return;
+        }
+        
+        // Register a stream handler factory for the JNDI protocol
+        URLStreamHandlerFactory streamHandlerFactory =
+                DirContextURLStreamHandlerFactory.getInstance();
+        if (first) {
+            first = false;
+            try {
+                URL.setURLStreamHandlerFactory(streamHandlerFactory);
+            } catch (Exception e) {
+                // Log and continue anyway, this is not critical
+                log.error("Error registering jndi stream handler", e);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                // This is likely a dual registration
+                log.info("Dual registration of jndi stream handler: " 
+                         + t.getMessage());
+            }
+        }
+
+        // Construct a class loader based on our current repositories list
+        try {
+
+            classLoader = createClassLoader();
+            classLoader.setResources(container.getResources());
+            classLoader.setDelegate(this.delegate);
+            classLoader.setSearchExternalFirst(searchExternalFirst);
+            if (container instanceof StandardContext) {
+                classLoader.setAntiJARLocking(
+                        ((StandardContext) container).getAntiJARLocking());
+                classLoader.setClearReferencesStatic(
+                        ((StandardContext) container).getClearReferencesStatic());
+                classLoader.setClearReferencesStopThreads(
+                        ((StandardContext) container).getClearReferencesStopThreads());
+                classLoader.setClearReferencesStopTimerThreads(
+                        ((StandardContext) container).getClearReferencesStopTimerThreads());
+                classLoader.setClearReferencesHttpClientKeepAliveThread(
+                        ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
+            }
+
+            for (int i = 0; i < repositories.length; i++) {
+                classLoader.addRepository(repositories[i]);
+            }
+
+            // Configure our repositories
+            setRepositories();
+            setClassPath();
+
+            setPermissions();
+
+            ((Lifecycle) classLoader).start();
+
+            // Binding the Webapp class loader to the directory context
+            DirContextURLStreamHandler.bind(classLoader,
+                    this.container.getResources());
+
+            StandardContext ctx=(StandardContext)container;
+            String contextName = ctx.getName();
+            if (!contextName.startsWith("/")) {
+                contextName = "/" + contextName;
+            }   
+            ObjectName cloname = new ObjectName
+                (MBeanUtils.getDomain(ctx) + ":type=WebappClassLoader,context="
+                 + contextName + ",host=" + ctx.getParent().getName());
+            Registry.getRegistry(null, null)
+                .registerComponent(classLoader, cloname, null);
+
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error( "LifecycleException ", t );
+            throw new LifecycleException("start: ", t);
+        }
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * Stop associated {@link ClassLoader} and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("webappLoader.stopping"));
+
+        setState(LifecycleState.STOPPING);
+
+        // Remove context attributes as appropriate
+        if (container instanceof Context) {
+            ServletContext servletContext =
+                ((Context) container).getServletContext();
+            servletContext.removeAttribute(Globals.CLASS_PATH_ATTR);
+        }
+
+        // Throw away our current class loader
+        ((Lifecycle) classLoader).stop();
+        DirContextURLStreamHandler.unbind(classLoader);
+
+        try {
+            StandardContext ctx=(StandardContext)container;
+            String contextName = ctx.getName();
+            if (!contextName.startsWith("/")) {
+                contextName = "/" + contextName;
+            }   
+            ObjectName cloname = new ObjectName
+                (MBeanUtils.getDomain(ctx) + ":type=WebappClassLoader,context="
+                 + contextName + ",host=" + ctx.getParent().getName());
+            Registry.getRegistry(null, null).unregisterComponent(cloname);
+        } catch (Exception e) {
+            log.error("LifecycleException ", e);
+        }
+
+        classLoader = null;
+    }
+
+
+    // ----------------------------------------- PropertyChangeListener Methods
+
+
+    /**
+     * Process property change events from our associated Context.
+     *
+     * @param event The property change event that has occurred
+     */
+    @Override
+    public void propertyChange(PropertyChangeEvent event) {
+
+        // Validate the source of this event
+        if (!(event.getSource() instanceof Context))
+            return;
+
+        // Process a relevant property change
+        if (event.getPropertyName().equals("reloadable")) {
+            try {
+                setReloadable
+                    ( ((Boolean) event.getNewValue()).booleanValue() );
+            } catch (NumberFormatException e) {
+                log.error(sm.getString("webappLoader.reloadable",
+                                 event.getNewValue().toString()));
+            }
+        }
+
+    }
+
+
+    // ------------------------------------------------------- Private Methods
+
+
+    /**
+     * Create associated classLoader.
+     */
+    private WebappClassLoader createClassLoader()
+        throws Exception {
+
+        Class<?> clazz = Class.forName(loaderClass);
+        WebappClassLoader classLoader = null;
+
+        if (parentClassLoader == null) {
+            parentClassLoader = container.getParentClassLoader();
+        }
+        Class<?>[] argTypes = { ClassLoader.class };
+        Object[] args = { parentClassLoader };
+        Constructor<?> constr = clazz.getConstructor(argTypes);
+        classLoader = (WebappClassLoader) constr.newInstance(args);
+
+        return classLoader;
+
+    }
+
+
+    /**
+     * Configure associated class loader permissions.
+     */
+    private void setPermissions() {
+
+        if (!Globals.IS_SECURITY_ENABLED)
+            return;
+        if (!(container instanceof Context))
+            return;
+
+        // Tell the class loader the root of the context
+        ServletContext servletContext =
+            ((Context) container).getServletContext();
+
+        // Assigning permissions for the work directory
+        File workDir =
+            (File) servletContext.getAttribute(ServletContext.TEMPDIR);
+        if (workDir != null) {
+            try {
+                String workDirPath = workDir.getCanonicalPath();
+                classLoader.addPermission
+                    (new FilePermission(workDirPath, "read,write"));
+                classLoader.addPermission
+                    (new FilePermission(workDirPath + File.separator + "-", 
+                                        "read,write,delete"));
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+
+        try {
+
+            URL rootURL = servletContext.getResource("/");
+            classLoader.addPermission(rootURL);
+
+            String contextRoot = servletContext.getRealPath("/");
+            if (contextRoot != null) {
+                try {
+                    contextRoot = (new File(contextRoot)).getCanonicalPath();
+                    classLoader.addPermission(contextRoot);
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+
+            URL classesURL = servletContext.getResource("/WEB-INF/classes/");
+            classLoader.addPermission(classesURL);
+            URL libURL = servletContext.getResource("/WEB-INF/lib/");
+            classLoader.addPermission(libURL);
+
+            if (contextRoot != null) {
+
+                if (libURL != null) {
+                    File rootDir = new File(contextRoot);
+                    File libDir = new File(rootDir, "WEB-INF/lib/");
+                    try {
+                        String path = libDir.getCanonicalPath();
+                        classLoader.addPermission(path);
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+
+            } else {
+
+                if (workDir != null) {
+                    if (libURL != null) {
+                        File libDir = new File(workDir, "WEB-INF/lib/");
+                        try {
+                            String path = libDir.getCanonicalPath();
+                            classLoader.addPermission(path);
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                    if (classesURL != null) {
+                        File classesDir = new File(workDir, "WEB-INF/classes/");
+                        try {
+                            String path = classesDir.getCanonicalPath();
+                            classLoader.addPermission(path);
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                }
+
+            }
+
+        } catch (MalformedURLException e) {
+            // Ignore
+        }
+
+    }
+
+
+    /**
+     * Configure the repositories for our class loader, based on the
+     * associated Context.
+     * @throws IOException 
+     */
+    private void setRepositories() throws IOException {
+
+        if (!(container instanceof Context))
+            return;
+        ServletContext servletContext =
+            ((Context) container).getServletContext();
+        if (servletContext == null)
+            return;
+
+        loaderRepositories=new ArrayList<String>();
+        // Loading the work directory
+        File workDir =
+            (File) servletContext.getAttribute(ServletContext.TEMPDIR);
+        if (workDir == null) {
+            log.info("No work dir for " + servletContext);
+        }
+
+        if( log.isDebugEnabled() && workDir != null) 
+            log.debug(sm.getString("webappLoader.deploy", workDir.getAbsolutePath()));
+
+        classLoader.setWorkDir(workDir);
+
+        DirContext resources = container.getResources();
+
+        // Setting up the class repository (/WEB-INF/classes), if it exists
+
+        String classesPath = "/WEB-INF/classes";
+        DirContext classes = null;
+
+        try {
+            Object object = resources.lookup(classesPath);
+            if (object instanceof DirContext) {
+                classes = (DirContext) object;
+            }
+        } catch(NamingException e) {
+            // Silent catch: it's valid that no /WEB-INF/classes collection
+            // exists
+        }
+
+        if (classes != null) {
+
+            File classRepository = null;
+
+            String absoluteClassesPath =
+                servletContext.getRealPath(classesPath);
+
+            if (absoluteClassesPath != null) {
+
+                classRepository = new File(absoluteClassesPath);
+
+            } else {
+
+                classRepository = new File(workDir, classesPath);
+                if (!classRepository.isDirectory()) {
+                    if (!classRepository.mkdirs())
+                        throw new IOException(
+                                sm.getString("webappLoader.mkdirFailure"));
+                }
+                if (!copyDir(classes, classRepository)) {
+                    throw new IOException(
+                            sm.getString("webappLoader.copyFailure"));
+                }
+
+            }
+
+            if(log.isDebugEnabled())
+                log.debug(sm.getString("webappLoader.classDeploy", classesPath,
+                             classRepository.getAbsolutePath()));
+
+
+            // Adding the repository to the class loader
+            classLoader.addRepository(classesPath + "/", classRepository);
+            loaderRepositories.add(classesPath + "/" );
+
+        }
+
+        // Setting up the JAR repository (/WEB-INF/lib), if it exists
+
+        String libPath = "/WEB-INF/lib";
+
+        classLoader.setJarPath(libPath);
+
+        DirContext libDir = null;
+        // Looking up directory /WEB-INF/lib in the context
+        try {
+            Object object = resources.lookup(libPath);
+            if (object instanceof DirContext)
+                libDir = (DirContext) object;
+        } catch(NamingException e) {
+            // Silent catch: it's valid that no /WEB-INF/lib collection
+            // exists
+        }
+
+        if (libDir != null) {
+
+            boolean copyJars = false;
+            String absoluteLibPath = servletContext.getRealPath(libPath);
+
+            File destDir = null;
+
+            if (absoluteLibPath != null) {
+                destDir = new File(absoluteLibPath);
+            } else {
+                copyJars = true;
+                destDir = new File(workDir, libPath);
+                if (!destDir.isDirectory()) {
+                    if (!destDir.mkdirs())
+                        throw new IOException(
+                                sm.getString("webappLoader.mkdirFailure"));
+                }
+            }
+
+            // Looking up directory /WEB-INF/lib in the context
+            NamingEnumeration<NameClassPair> enumeration = null;
+            try {
+                enumeration = libDir.list("");
+            } catch (NamingException e) {
+                IOException ioe = new IOException(sm.getString(
+                        "webappLoader.namingFailure", libPath));
+                ioe.initCause(e);
+                throw ioe;
+            }
+            while (enumeration.hasMoreElements()) {
+                NameClassPair ncPair = enumeration.nextElement();
+                String filename = libPath + "/" + ncPair.getName();
+                if (!filename.endsWith(".jar"))
+                    continue;
+
+                // Copy JAR in the work directory, always (the JAR file
+                // would get locked otherwise, which would make it
+                // impossible to update it or remove it at runtime)
+                File destFile = new File(destDir, ncPair.getName());
+
+                if( log.isDebugEnabled())
+                log.debug(sm.getString("webappLoader.jarDeploy", filename,
+                                 destFile.getAbsolutePath()));
+
+                // Bug 45403 - Explicitly call lookup() on the name to check
+                // that the resource is readable. We cannot use resources
+                // returned by listBindings(), because that lists all of them,
+                // but does not perform the necessary checks on each.
+                Object obj = null;
+                try {
+                    obj = libDir.lookup(ncPair.getName());
+                } catch (NamingException e) {
+                    IOException ioe = new IOException(sm.getString(
+                            "webappLoader.namingFailure", filename));
+                    ioe.initCause(e);
+                    throw ioe;
+                }
+                
+                if (!(obj instanceof Resource))
+                    continue;
+                    
+                Resource jarResource = (Resource) obj;
+                    
+                if (copyJars) {
+                    if (!copy(jarResource.streamContent(),
+                              new FileOutputStream(destFile))) {
+                        throw new IOException(
+                                sm.getString("webappLoader.copyFailure"));
+                    }
+                }
+
+                try {
+                    JarFile jarFile = new JarFile(destFile);
+                    classLoader.addJar(filename, jarFile, destFile);
+                } catch (Exception ex) {
+                    // Catch the exception if there is an empty jar file
+                    // Should ignore and continue loading other jar files 
+                    // in the dir
+                }
+                    
+                loaderRepositories.add( filename );
+            }
+        }
+    }
+
+
+    /**
+     * Set the appropriate context attribute for our class path.  This
+     * is required only because Jasper depends on it.
+     */
+    private void setClassPath() {
+
+        // Validate our current state information
+        if (!(container instanceof Context))
+            return;
+        ServletContext servletContext =
+            ((Context) container).getServletContext();
+        if (servletContext == null)
+            return;
+
+        if (container instanceof StandardContext) {
+            String baseClasspath = 
+                ((StandardContext) container).getCompilerClasspath();
+            if (baseClasspath != null) {
+                servletContext.setAttribute(Globals.CLASS_PATH_ATTR,
+                                            baseClasspath);
+                return;
+            }
+        }
+
+        StringBuilder classpath = new StringBuilder();
+
+        // Assemble the class path information from our class loader chain
+        ClassLoader loader = getClassLoader();
+        int layers = 0;
+        int n = 0;
+        while (loader != null) {
+            if (!(loader instanceof URLClassLoader)) {
+                String cp=getClasspath( loader );
+                if( cp==null ) {
+                    log.info( "Unknown loader " + loader + " " + loader.getClass());
+                } else {
+                    if (n > 0) 
+                        classpath.append(File.pathSeparator);
+                    classpath.append(cp);
+                    n++;
+                }
+                break;
+                //continue;
+            }
+            URL repositories[] =
+                ((URLClassLoader) loader).getURLs();
+            for (int i = 0; i < repositories.length; i++) {
+                String repository = repositories[i].toString();
+                if (repository.startsWith("file://"))
+                    repository = repository.substring(7);
+                else if (repository.startsWith("file:"))
+                    repository = repository.substring(5);
+                else if (repository.startsWith("jndi:"))
+                    repository =
+                        servletContext.getRealPath(repository.substring(5));
+                else
+                    continue;
+                if (repository == null)
+                    continue;
+                if (n > 0)
+                    classpath.append(File.pathSeparator);
+                classpath.append(repository);
+                n++;
+            }
+            loader = loader.getParent();
+            layers++;
+        }
+
+        this.classpath=classpath.toString();
+
+        // Store the assembled class path as a servlet context attribute
+        servletContext.setAttribute(Globals.CLASS_PATH_ATTR,
+                                    classpath.toString());
+
+    }
+
+    // try to extract the classpath from a loader that is not URLClassLoader
+    private String getClasspath( ClassLoader loader ) {
+        try {
+            Method m=loader.getClass().getMethod("getClasspath", new Class[] {});
+            if( log.isTraceEnabled())
+                log.trace("getClasspath " + m );
+            if( m==null ) return null;
+            Object o=m.invoke( loader, new Object[] {} );
+            if( log.isDebugEnabled() )
+                log.debug("gotClasspath " + o);
+            if( o instanceof String )
+                return (String)o;
+            return null;
+        } catch( Exception ex ) {
+            if (log.isDebugEnabled())
+                log.debug("getClasspath ", ex);
+        }
+        return null;
+    }
+
+    /**
+     * Copy directory.
+     */
+    private boolean copyDir(DirContext srcDir, File destDir) {
+
+        try {
+
+            NamingEnumeration<NameClassPair> enumeration = srcDir.list("");
+            while (enumeration.hasMoreElements()) {
+                NameClassPair ncPair = enumeration.nextElement();
+                String name = ncPair.getName();
+                Object object = srcDir.lookup(name);
+                File currentFile = new File(destDir, name);
+                if (object instanceof Resource) {
+                    InputStream is = ((Resource) object).streamContent();
+                    OutputStream os = new FileOutputStream(currentFile);
+                    if (!copy(is, os))
+                        return false;
+                } else if (object instanceof InputStream) {
+                    OutputStream os = new FileOutputStream(currentFile);
+                    if (!copy((InputStream) object, os))
+                        return false;
+                } else if (object instanceof DirContext) {
+                    if (!currentFile.isDirectory() && !currentFile.mkdir())
+                        return false;
+                    if (!copyDir((DirContext) object, currentFile))
+                        return false;
+                }
+            }
+
+        } catch (NamingException e) {
+            return false;
+        } catch (IOException e) {
+            return false;
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Copy a file to the specified temp directory. This is required only
+     * because Jasper depends on it.
+     */
+    private boolean copy(InputStream is, OutputStream os) {
+
+        try {
+            byte[] buf = new byte[4096];
+            while (true) {
+                int len = is.read(buf);
+                if (len < 0)
+                    break;
+                os.write(buf, 0, len);
+            }
+            is.close();
+            os.close();
+        } catch (IOException e) {
+            return false;
+        }
+
+        return true;
+
+    }
+
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( WebappLoader.class );
+
+
+    @Override
+    protected String getDomainInternal() {
+        return MBeanUtils.getDomain(container);
+    }
+
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+        
+        StringBuilder name = new StringBuilder("type=Loader");
+        
+        if (container instanceof Context) {
+            name.append(",context=");
+            Context context = (Context) container;
+            
+            String contextName = context.getName();
+            if (!contextName.startsWith("/")) {
+                name.append("/");
+            }   
+            name.append(contextName);
+            
+            name.append(",host=");
+            name.append(context.getParent().getName());
+        } else {
+            // Unlikely / impossible? Handle it to be safe
+            name.append(",container=");
+            name.append(container.getName());
+        }
+        
+        return name.toString();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/loader/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/mbeans-descriptors.xml
new file mode 100644
index 0000000..8184882
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/loader/mbeans-descriptors.xml
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<mbeans-descriptors>
+
+  <mbean         name="WebappLoader"
+          description="Classloader implementation which is specialized for handling web applications"
+               domain="Catalina"
+                group="Loader"
+                 type="org.apache.catalina.loader.WebappLoader">
+                 
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="delegate"
+          description="The 'follow standard delegation model' flag that will be used to configure our ClassLoader"
+                 type="boolean"/>
+
+    <attribute   name="reloadable"
+          description="The reloadable flag for this Loader"
+                 type="boolean"/>
+
+    <attribute   name="searchExternalFirst"
+          description="The searchExternalFirst flag for this Loader"
+                 type="boolean"/>
+
+    <attribute   name="repositories"
+          description="Extra repositories managed by this loader"
+                 type="[Ljava.lang.String;"/>
+
+    <attribute   name="repositoriesString"
+          description="Extra repositories managed by this loader"
+             writeable="false" 
+                 type="java.lang.String"/>
+
+    <attribute   name="stateName"
+          description="The name of the LifecycleState that this component is currently in"
+                 type="java.lang.String"
+                 writeable="false"/>
+
+    <attribute   name="loaderRepositories"
+          description="Repositories set in the real loader"
+                 type="[Ljava.lang.String;"
+            writeable="false" />
+
+    <attribute   name="loaderRepositoriesString"
+          description="Repositories set in the real loader"
+                 type="java.lang.String"
+             writeable="false" />
+
+    <operation   name="toString"
+          description="Info about the loader"
+               impact="INFO"
+           returnType="String">
+    </operation>
+  </mbean>
+
+
+  <mbean         name="VirtualWebappLoader"
+          description="Extension of the webapp class loader with additional features"
+               domain="Catalina"
+                group="Loader"
+                 type="org.apache.catalina.loader.VirtualWebappLoader">
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="delegate"
+          description="The 'follow standard delegation model' flag that will be used to configure our ClassLoader"
+                 type="boolean"/>
+
+    <attribute   name="reloadable"
+          description="The reloadable flag for this Loader"
+                 type="boolean"/>
+
+    <attribute   name="searchVirtualFirst"
+          description="The searchVirtualFirst flag for this Loader"
+                 type="boolean"/>
+
+    <attribute   name="repositories"
+          description="Extra repositories managed by this loader"
+                 type="[Ljava.lang.String;"/>
+
+    <attribute   name="repositoriesString"
+          description="Extra repositories managed by this loader"
+             writeable="false" 
+                 type="java.lang.String"/>
+
+    <attribute   name="stateName"
+          description="The name of the LifecycleState that this component is currently in"
+                 type="java.lang.String"
+                 writeable="false"/>
+
+    <attribute   name="loaderRepositories"
+          description="Repositories set in the real loader"
+                 type="[Ljava.lang.String;"
+            writeable="false" />
+
+    <attribute   name="loaderRepositoriesString"
+          description="Repositories set in the real loader"
+                 type="java.lang.String"
+             writeable="false" />
+
+    <operation   name="toString"
+          description="Info about the loader"
+               impact="INFO"
+           returnType="String">
+    </operation>
+  </mbean>
+
+
+  <mbean         name="WebappClassLoader"
+          description="Classloader implementation which is specialized for handling web applications"
+               domain="Catalina"
+                group="Loader"
+                 type="org.apache.catalina.loader.WebappClassLoader">
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="contextName"
+          description="Name of the webapp context"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="delegate"
+          description="The 'follow standard delegation model' flag that will be used to configure our ClassLoader"
+                 type="boolean"/>
+
+    <attribute   name="antiJARLocking"
+          description="The antiJARLocking flag for this Loader"
+                 type="boolean"/>
+
+    <attribute   name="searchExternalFirst"
+          description="The searchExternalFirst flag for this Loader"
+                 type="boolean"/>
+
+    <attribute   name="stateName"
+          description="The name of the LifecycleState that this component is currently in"
+                 type="java.lang.String"
+                 writeable="false"/>
+
+    <attribute   name="URLs"
+          description="The URLs of this loader"
+                 type="[Ljava.net.URL;"/>
+
+    <attribute   name="jarPath"
+          description="The jarPath of this loader"
+             writeable="false" 
+                 type="java.lang.String"/>
+
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/Constants.java
new file mode 100644
index 0000000..c0b6842
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/Constants.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.manager;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.manager";
+
+    public static final String HTML_HEADER_SECTION;
+    public static final String BODY_HEADER_SECTION;
+    public static final String MESSAGE_SECTION;
+    public static final String MANAGER_SECTION;
+    public static final String SERVER_HEADER_SECTION;
+    public static final String SERVER_ROW_SECTION;
+    public static final String HTML_TAIL_SECTION;
+    
+    static {
+        HTML_HEADER_SECTION =
+            "<html>\n" +
+            "<head>\n" +
+            "<style>\n" +
+            org.apache.catalina.util.TomcatCSS.TOMCAT_CSS + "\n" +
+            "  table {\n" +
+            "    width: 100%;\n" +
+            "  }\n" +
+            "  td.page-title {\n" +
+            "    text-align: center;\n" +
+            "    vertical-align: top;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    font-weight: bold;\n" +
+            "    background: white;\n" +
+            "    color: black;\n" +
+            "  }\n" +
+            "  td.title {\n" +
+            "    text-align: left;\n" +
+            "    vertical-align: top;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    font-style:italic;\n" +
+            "    font-weight: bold;\n" +
+            "    background: #D2A41C;\n" +
+            "  }\n" +
+            "  td.header-left {\n" +
+            "    text-align: left;\n" +
+            "    vertical-align: top;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    font-weight: bold;\n" +
+            "    background: #FFDC75;\n" +
+            "  }\n" +
+            "  td.header-center {\n" +
+            "    text-align: center;\n" +
+            "    vertical-align: top;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    font-weight: bold;\n" +
+            "    background: #FFDC75;\n" +
+            "  }\n" +
+            "  td.row-left {\n" +
+            "    text-align: left;\n" +
+            "    vertical-align: middle;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    color: black;\n" +
+            "  }\n" +
+            "  td.row-center {\n" +
+            "    text-align: center;\n" +
+            "    vertical-align: middle;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    color: black;\n" +
+            "  }\n" +
+            "  td.row-right {\n" +
+            "    text-align: right;\n" +
+            "    vertical-align: middle;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    color: black;\n" +
+            "  }\n" +
+            "  TH {\n" +
+            "    text-align: center;\n" +
+            "    vertical-align: top;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    font-weight: bold;\n" +
+            "    background: #FFDC75;\n" +
+            "  }\n" +
+            "  TD {\n" +
+            "    text-align: center;\n" +
+            "    vertical-align: middle;\n" +
+            "    font-family:sans-serif,Tahoma,Arial;\n" +
+            "    color: black;\n" +
+            "  }\n" +
+            "  form {\n" +
+            "    margin: 1;\n" +
+            "  }\n" +
+            "  form.inline {\n" +
+            "    display: inline;\n" +
+            "  }\n" +
+            "</style>\n";
+    
+        BODY_HEADER_SECTION =
+            "<title>{0}</title>\n" +
+            "</head>\n" +
+            "\n" +
+            "<body bgcolor=\"#FFFFFF\">\n" +
+            "\n" +
+            "<table cellspacing=\"4\" border=\"0\">\n" +
+            " <tr>\n" +
+            "  <td colspan=\"2\">\n" +
+            "   <a href=\"http://www.apache.org/\">\n" +
+            "    <img border=\"0\" alt=\"The Apache Software Foundation\" align=\"left\"\n" +
+            "         src=\"{0}/images/asf-logo.gif\">\n" +
+            "   </a>\n" +
+            "   <a href=\"http://tomcat.apache.org/\">\n" +
+            "    <img border=\"0\" alt=\"The Tomcat Servlet/JSP Container\"\n" +
+            "         align=\"right\" src=\"{0}/images/tomcat.gif\">\n" +
+            "   </a>\n" +
+            "  </td>\n" +
+            " </tr>\n" +
+            "</table>\n" +
+            "<hr size=\"1\" noshade=\"noshade\">\n" +
+            "<table cellspacing=\"4\" border=\"0\">\n" +
+            " <tr>\n" +
+            "  <td class=\"page-title\" bordercolor=\"#000000\" " +
+            "align=\"left\" nowrap>\n" +
+            "   <font size=\"+2\">{1}</font>\n" +
+            "  </td>\n" +
+            " </tr>\n" +
+            "</table>\n" +
+            "<br>\n" +
+            "\n";
+    
+        MESSAGE_SECTION =
+            "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+            " <tr>\n" +
+            "  <td class=\"row-left\" width=\"10%\">" +
+            "<small><strong>{0}</strong></small>&nbsp;</td>\n" +
+            "  <td class=\"row-left\"><pre>{1}</pre></td>\n" +
+            " </tr>\n" +
+            "</table>\n" +
+            "<br>\n" +
+            "\n";
+    
+        MANAGER_SECTION =
+            "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+            "<tr>\n" +
+            " <td colspan=\"4\" class=\"title\">{0}</td>\n" +
+            "</tr>\n" +
+            " <tr>\n" +
+            "  <td class=\"row-left\"><a href=\"{1}\">{2}</a></td>\n" +
+            "  <td class=\"row-center\"><a href=\"{3}\">{4}</a></td>\n" +
+            "  <td class=\"row-center\"><a href=\"{5}\">{6}</a></td>\n" +
+            "  <td class=\"row-right\"><a href=\"{7}\">{8}</a></td>\n" +
+            " </tr>\n" +
+            "</table>\n" +
+            "<br>\n" +
+            "\n";
+    
+        SERVER_HEADER_SECTION =
+            "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+            "<tr>\n" +
+            " <td colspan=\"6\" class=\"title\">{0}</td>\n" +
+            "</tr>\n" +
+            "<tr>\n" +
+            " <td class=\"header-center\"><small>{1}</small></td>\n" +
+            " <td class=\"header-center\"><small>{2}</small></td>\n" +
+            " <td class=\"header-center\"><small>{3}</small></td>\n" +
+            " <td class=\"header-center\"><small>{4}</small></td>\n" +
+            " <td class=\"header-center\"><small>{5}</small></td>\n" +
+            " <td class=\"header-center\"><small>{6}</small></td>\n" +
+            "</tr>\n";
+    
+        SERVER_ROW_SECTION =
+            "<tr>\n" +
+            " <td class=\"row-center\"><small>{0}</small></td>\n" +
+            " <td class=\"row-center\"><small>{1}</small></td>\n" +
+            " <td class=\"row-center\"><small>{2}</small></td>\n" +
+            " <td class=\"row-center\"><small>{3}</small></td>\n" +
+            " <td class=\"row-center\"><small>{4}</small></td>\n" +
+            " <td class=\"row-center\"><small>{5}</small></td>\n" +
+            "</tr>\n" +
+            "</table>\n" +
+            "<br>\n" +
+            "\n";
+    
+        HTML_TAIL_SECTION =
+            "<hr size=\"1\" noshade=\"noshade\">\n" +
+            "<center><font size=\"-1\" color=\"#525D76\">\n" +
+            " <em>Copyright &copy; 1999-2011, Apache Software Foundation</em>" +
+            "</font></center>\n" +
+            "\n" +
+            "</body>\n" +
+            "</html>";
+    }
+
+    public static final String CHARSET="utf-8";
+
+    public static final String XML_DECLARATION =
+        "<?xml version=\"1.0\" encoding=\""+CHARSET+"\"?>";
+
+    public static final String XML_STYLE =
+        "<?xml-stylesheet type=\"text/xsl\" href=\"xform.xsl\" ?>";
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/DummyProxySession.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/DummyProxySession.java
new file mode 100644
index 0000000..3bd7bac
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/DummyProxySession.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.manager;
+
+import java.security.Principal;
+import java.util.Iterator;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.SessionListener;
+
+public class DummyProxySession implements Session {
+
+    private String sessionId;
+    
+    public DummyProxySession(String sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    @Override
+    public void access() {
+        // NOOP
+    }
+
+    @Override
+    public void addSessionListener(SessionListener listener) {
+        // NOOP
+    }
+
+    @Override
+    public void endAccess() {
+        // NOOP
+    }
+
+    @Override
+    public void expire() {
+        // NOOP
+    }
+
+    @Override
+    public String getAuthType() {
+        return null;
+    }
+
+    @Override
+    public long getCreationTime() {
+        return 0;
+    }
+
+    @Override
+    public long getCreationTimeInternal() {
+        return 0;
+    }
+
+    @Override
+    public String getId() {
+        return sessionId;
+    }
+
+    @Override
+    public String getIdInternal() {
+        return sessionId;
+    }
+
+    @Override
+    public String getInfo() {
+        return null;
+    }
+
+    @Override
+    public long getLastAccessedTime() {
+        return 0;
+    }
+
+    @Override
+    public long getLastAccessedTimeInternal() {
+        return 0;
+    }
+
+    @Override
+    public Manager getManager() {
+        return null;
+    }
+
+    @Override
+    public int getMaxInactiveInterval() {
+        return 0;
+    }
+
+    @Override
+    public Object getNote(String name) {
+        return null;
+    }
+
+    @Override
+    public Iterator<String> getNoteNames() {
+        return null;
+    }
+
+    @Override
+    public Principal getPrincipal() {
+        return null;
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return null;
+    }
+
+    @Override
+    public long getThisAccessedTime() {
+        return 0;
+    }
+
+    @Override
+    public long getThisAccessedTimeInternal() {
+        return 0;
+    }
+
+    @Override
+    public boolean isValid() {
+        return false;
+    }
+
+    @Override
+    public void recycle() {
+        // NOOP
+    }
+
+    @Override
+    public void removeNote(String name) {
+        // NOOP
+    }
+
+    @Override
+    public void removeSessionListener(SessionListener listener) {
+        // NOOP
+    }
+
+    @Override
+    public void setAuthType(String authType) {
+        // NOOP
+    }
+
+    @Override
+    public void setCreationTime(long time) {
+        // NOOP
+    }
+
+    @Override
+    public void setId(String id) {
+        this.sessionId = id;
+    }
+
+    @Override
+    public void setManager(Manager manager) {
+        // NOOP
+    }
+
+    @Override
+    public void setMaxInactiveInterval(int interval) {
+        // NOOP
+    }
+
+    @Override
+    public void setNew(boolean isNew) {
+        // NOOP
+    }
+
+    @Override
+    public void setNote(String name, Object value) {
+        // NOOP
+    }
+
+    @Override
+    public void setPrincipal(Principal principal) {
+        // NOOP
+    }
+
+    @Override
+    public void setValid(boolean isValid) {
+        // NOOP
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/HTMLManagerServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/HTMLManagerServlet.java
new file mode 100644
index 0000000..d1c8f0b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/HTMLManagerServlet.java
@@ -0,0 +1,1369 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.manager;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.DistributedManager;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.manager.util.BaseSessionComparator;
+import org.apache.catalina.manager.util.ReverseComparator;
+import org.apache.catalina.manager.util.SessionUtils;
+import org.apache.catalina.util.ContextName;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.catalina.util.URLEncoder;
+import org.apache.tomcat.util.http.fileupload.ParameterParser;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+* Servlet that enables remote management of the web applications deployed
+* within the same virtual host as this web application is.  Normally, this
+* functionality will be protected by a security constraint in the web
+* application deployment descriptor.  However, this requirement can be
+* relaxed during testing.
+* <p>
+* The difference between the <code>ManagerServlet</code> and this
+* Servlet is that this Servlet prints out a HTML interface which
+* makes it easier to administrate.
+* <p>
+* However if you use a software that parses the output of
+* <code>ManagerServlet</code> you won't be able to upgrade
+* to this Servlet since the output are not in the
+* same format ar from <code>ManagerServlet</code>
+*
+* @author Bip Thelin
+* @author Malcolm Edgar
+* @author Glenn L. Nielsen
+* @version $Id: HTMLManagerServlet.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+* @see ManagerServlet
+*/
+
+public final class HTMLManagerServlet extends ManagerServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    protected static final URLEncoder URL_ENCODER; 
+    protected static final String APPLICATION_MESSAGE = "message";
+    protected static final String APPLICATION_ERROR = "error";
+    
+    protected static final String sessionsListJspPath  = "/WEB-INF/jsp/sessionsList.jsp";
+    protected static final String sessionDetailJspPath = "/WEB-INF/jsp/sessionDetail.jsp";
+
+    static {
+        URL_ENCODER = new URLEncoder();
+        // '/' should not be encoded in context paths
+        URL_ENCODER.addSafeCharacter('/');
+    }
+    
+    private final Random randomSource = new Random();
+    
+    private boolean showProxySessions = false;
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        StringManager smClient = getStringManager(request);
+        
+        // Identify the request parameters that we need
+        // By obtaining the command from the pathInfo, per-command security can
+        // be configured in web.xml
+        String command = request.getPathInfo();
+
+        String path = request.getParameter("path");
+        ContextName cn = null;
+        if (path != null) {
+            cn = new ContextName(path, request.getParameter("version"));
+        }
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/html; charset=" + Constants.CHARSET);
+
+        String message = "";
+        // Process the requested command
+        if (command == null || command.equals("/")) {
+            // No command == list
+        } else if (command.equals("/list")) {
+            // List always displayed - nothing to do here
+        } else if (command.equals("/sessions")) {
+            try {
+                doSessions(cn, request, response, smClient);
+                return;
+            } catch (Exception e) {
+                log("HTMLManagerServlet.sessions[" + cn + "]", e);
+                message = smClient.getString("managerServlet.exception",
+                        e.toString());
+            }
+        } else if (command.equals("/upload") || command.equals("/deploy") ||
+                command.equals("/reload") || command.equals("/undeploy") ||
+                command.equals("/expire") || command.equals("/start") ||
+                command.equals("/stop")) {
+            message =
+                smClient.getString("managerServlet.postCommand", command);
+        } else {
+            message =
+                smClient.getString("managerServlet.unknownCommand", command);
+        }
+
+        list(request, response, message, smClient);
+    }
+
+    /**
+     * Process a POST request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    public void doPost(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        StringManager smClient = getStringManager(request);
+
+        // Identify the request parameters that we need
+        // By obtaining the command from the pathInfo, per-command security can
+        // be configured in web.xml
+        String command = request.getPathInfo();
+
+        String path = request.getParameter("path");
+        ContextName cn = null;
+        if (path != null) {
+            cn = new ContextName(path, request.getParameter("version"));
+        }
+        String deployPath = request.getParameter("deployPath");
+        ContextName deployCn = null;
+        if (deployPath != null) {
+            deployCn = new ContextName(deployPath,
+                    request.getParameter("deployVersion")); 
+        }
+        String deployConfig = request.getParameter("deployConfig");
+        String deployWar = request.getParameter("deployWar");
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/html; charset=" + Constants.CHARSET);
+
+        String message = "";
+
+        if (command == null || command.length() == 0) {
+            // No command == list
+            // List always displayed -> do nothing
+        } else if (command.equals("/upload")) {
+            message = upload(request, smClient);
+        } else if (command.equals("/deploy")) {
+            message = deployInternal(deployConfig, deployCn, deployWar,
+                    smClient);
+        } else if (command.equals("/reload")) {
+            message = reload(cn, smClient);
+        } else if (command.equals("/undeploy")) {
+            message = undeploy(cn, smClient);
+        } else if (command.equals("/expire")) {
+            message = expireSessions(cn, request, smClient);
+        } else if (command.equals("/start")) {
+            message = start(cn, smClient);
+        } else if (command.equals("/stop")) {
+            message = stop(cn, smClient);
+        } else if (command.equals("/findleaks")) {
+            message = findleaks(smClient);
+        } else {
+            // Try GET
+            doGet(request,response);
+            return;
+        }
+
+        list(request, response, message, smClient);
+    }
+
+    /**
+     * Generate a once time token (nonce) for authenticating subsequent
+     * requests. This will also add the token to the session. The nonce
+     * generation is a simplified version of ManagerBase.generateSessionId().
+     * 
+     */
+    protected String generateNonce() {
+        byte random[] = new byte[16];
+
+        // Render the result as a String of hexadecimal digits
+        StringBuilder buffer = new StringBuilder();
+
+        randomSource.nextBytes(random);
+       
+        for (int j = 0; j < random.length; j++) {
+            byte b1 = (byte) ((random[j] & 0xf0) >> 4);
+            byte b2 = (byte) (random[j] & 0x0f);
+            if (b1 < 10)
+                buffer.append((char) ('0' + b1));
+            else
+                buffer.append((char) ('A' + (b1 - 10)));
+            if (b2 < 10)
+                buffer.append((char) ('0' + b2));
+            else
+                buffer.append((char) ('A' + (b2 - 10)));
+        }
+
+        return buffer.toString();
+    }
+
+    protected String upload(HttpServletRequest request, StringManager smClient)
+            throws IOException, ServletException {
+        String message = "";
+
+        Part warPart = null;
+        String filename = null;
+
+        Collection<Part> parts = request.getParts();
+        Iterator<Part> iter = parts.iterator();
+        
+        try {
+            while (iter.hasNext()) {
+                Part part = iter.next();
+                if (part.getName().equals("deployWar") && warPart == null) {
+                    warPart = part;
+                } else {
+                    part.delete();
+                }
+            }
+
+            while (true) {
+                if (warPart == null) {
+                    message = smClient.getString(
+                            "htmlManagerServlet.deployUploadNoFile");
+                    break;
+                }
+                filename =
+                    extractFilename(warPart.getHeader("Content-Disposition"));
+                if (!filename.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
+                    message = smClient.getString(
+                            "htmlManagerServlet.deployUploadNotWar", filename);
+                    break;
+                }
+                // Get the filename if uploaded name includes a path
+                if (filename.lastIndexOf('\\') >= 0) {
+                    filename =
+                        filename.substring(filename.lastIndexOf('\\') + 1);
+                }
+                if (filename.lastIndexOf('/') >= 0) {
+                    filename =
+                        filename.substring(filename.lastIndexOf('/') + 1);
+                }
+
+                // Identify the appBase of the owning Host of this Context
+                // (if any)
+                File file = new File(getAppBase(), filename);
+                if (file.exists()) {
+                    message = smClient.getString(
+                            "htmlManagerServlet.deployUploadWarExists",
+                            filename);
+                    break;
+                }
+                
+                ContextName cn = new ContextName(filename);
+                String name = cn.getName();
+
+                if ((host.findChild(name) != null) && !isDeployed(name)) {
+                    message = smClient.getString(
+                            "htmlManagerServlet.deployUploadInServerXml",
+                            filename);
+                    break;
+                }
+
+                if (!isServiced(name)) {
+                    addServiced(name);
+                    try {
+                        warPart.write(file.getAbsolutePath());
+                        // Perform new deployment
+                        check(name);
+                    } finally {
+                        removeServiced(name);
+                    }
+                }
+                break;
+            }
+        } catch(Exception e) {
+            message = smClient.getString
+                ("htmlManagerServlet.deployUploadFail", e.getMessage());
+            log(message, e);
+        } finally {
+            if (warPart != null) {
+                warPart.delete();
+            }
+            warPart = null;
+        }
+        return message;
+    }
+
+    /*
+     * Adapted from FileUploadBase.getFileName()
+     */
+    private String extractFilename(String cd) {
+        String fileName = null;
+        if (cd != null) {
+            String cdl = cd.toLowerCase(Locale.ENGLISH);
+            if (cdl.startsWith("form-data") || cdl.startsWith("attachment")) {
+                ParameterParser parser = new ParameterParser();
+                parser.setLowerCaseNames(true);
+                // Parameter parser can handle null input
+                Map<String,String> params =
+                    parser.parse(cd, ';');
+                if (params.containsKey("filename")) {
+                    fileName = params.get("filename");
+                    if (fileName != null) {
+                        fileName = fileName.trim();
+                    } else {
+                        // Even if there is no value, the parameter is present,
+                        // so we return an empty file name rather than no file
+                        // name.
+                        fileName = "";
+                    }
+                }
+            }
+        }
+        return fileName;
+    }
+
+    /**
+     * Deploy an application for the specified path from the specified
+     * web application archive.
+     *
+     * @param config URL of the context configuration file to be deployed
+     * @param cn Name of the application to be deployed
+     * @param war URL of the web application archive to be deployed
+     * @return message String
+     */
+    protected String deployInternal(String config, ContextName cn, String war,
+            StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.deploy(printWriter, config, cn, war, false, smClient);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Render a HTML list of the currently active Contexts in our virtual host,
+     * and memory and server status information.
+     *
+     * @param request The request
+     * @param response The response
+     * @param message a message to display
+     */
+    protected void list(HttpServletRequest request,
+                     HttpServletResponse response,
+                     String message,
+                     StringManager smClient) throws IOException {
+
+        if (debug >= 1)
+            log("list: Listing contexts for virtual host '" +
+                host.getName() + "'");
+
+        PrintWriter writer = response.getWriter();
+
+        // HTML Header Section
+        writer.print(Constants.HTML_HEADER_SECTION);
+
+        // Body Header Section
+        Object[] args = new Object[2];
+        args[0] = request.getContextPath();
+        args[1] = smClient.getString("htmlManagerServlet.title");
+        writer.print(MessageFormat.format
+                     (Constants.BODY_HEADER_SECTION, args));
+
+        // Message Section
+        args = new Object[3];
+        args[0] = smClient.getString("htmlManagerServlet.messageLabel");
+        if (message == null || message.length() == 0) {
+            args[1] = "OK";
+        } else {
+            args[1] = RequestUtil.filter(message);
+        }
+        writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args));
+
+        // Manager Section
+        args = new Object[9];
+        args[0] = smClient.getString("htmlManagerServlet.manager");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/list");
+        args[2] = smClient.getString("htmlManagerServlet.list");
+        args[3] = response.encodeURL
+            (request.getContextPath() + "/" +
+             smClient.getString("htmlManagerServlet.helpHtmlManagerFile"));
+        args[4] = smClient.getString("htmlManagerServlet.helpHtmlManager");
+        args[5] = response.encodeURL
+            (request.getContextPath() + "/" +
+             smClient.getString("htmlManagerServlet.helpManagerFile"));
+        args[6] = smClient.getString("htmlManagerServlet.helpManager");
+        args[7] = response.encodeURL
+            (request.getContextPath() + "/status");
+        args[8] = smClient.getString("statusServlet.title");
+        writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
+
+        // Apps Header Section
+        args = new Object[7];
+        args[0] = smClient.getString("htmlManagerServlet.appsTitle");
+        args[1] = smClient.getString("htmlManagerServlet.appsPath");
+        args[2] = smClient.getString("htmlManagerServlet.appsVersion");
+        args[3] = smClient.getString("htmlManagerServlet.appsName");
+        args[4] = smClient.getString("htmlManagerServlet.appsAvailable");
+        args[5] = smClient.getString("htmlManagerServlet.appsSessions");
+        args[6] = smClient.getString("htmlManagerServlet.appsTasks");
+        writer.print(MessageFormat.format(APPS_HEADER_SECTION, args));
+
+        // Apps Row Section
+        // Create sorted map of deployed applications by context name.
+        Container children[] = host.findChildren();
+        String contextNames[] = new String[children.length];
+        for (int i = 0; i < children.length; i++)
+            contextNames[i] = children[i].getName();
+
+        Arrays.sort(contextNames);
+        
+        String appsStart = smClient.getString("htmlManagerServlet.appsStart");
+        String appsStop = smClient.getString("htmlManagerServlet.appsStop");
+        String appsReload = smClient.getString("htmlManagerServlet.appsReload");
+        String appsUndeploy =
+            smClient.getString("htmlManagerServlet.appsUndeploy");
+        String appsExpire = smClient.getString("htmlManagerServlet.appsExpire");
+        String noVersion = "<i>" +
+            smClient.getString("htmlManagerServlet.noVersion") + "</i>";
+
+        boolean isHighlighted = true;
+        boolean isDeployed = true;
+        String highlightColor = null;
+
+        for (String contextName : contextNames) {
+            Context ctxt = (Context) host.findChild(contextName);
+            
+            if (ctxt != null) {
+                // Bugzilla 34818, alternating row colors
+                isHighlighted = !isHighlighted;
+                if(isHighlighted) {
+                    highlightColor = "#C3F3C3";
+                } else {
+                    highlightColor = "#FFFFFF";
+                }
+
+                String contextPath = ctxt.getPath();
+                String displayPath = contextPath;
+                if (displayPath.equals("")) {
+                    displayPath = "/";
+                }
+
+                StringBuilder tmp = new StringBuilder();
+                tmp.append("path=");
+                tmp.append(URL_ENCODER.encode(displayPath));
+                if (ctxt.getWebappVersion().length() > 0) {
+                    tmp.append("&version=");
+                    tmp.append(URL_ENCODER.encode(ctxt.getWebappVersion()));
+                }
+                String pathVersion = tmp.toString();
+
+                try {
+                    isDeployed = isDeployed(contextName);
+                } catch (Exception e) {
+                    // Assume false on failure for safety
+                    isDeployed = false;
+                }
+                
+                args = new Object[7];
+                args[0] = "<a href=\"" + URL_ENCODER.encode(displayPath)
+                        + "\">" + RequestUtil.filter(displayPath) + "</a>";
+                if ("".equals(ctxt.getWebappVersion())) {
+                    args[1] = noVersion;
+                } else {
+                    args[1] = RequestUtil.filter(ctxt.getWebappVersion());
+                }
+                if (ctxt.getDisplayName() == null) {
+                    args[2] = "&nbsp;";
+                } else {
+                    args[2] = RequestUtil.filter(ctxt.getDisplayName());
+                }
+                args[3] = Boolean.valueOf(ctxt.getAvailable());
+                args[4] = RequestUtil.filter(response.encodeURL(request.getContextPath() +
+                     "/html/sessions?" + pathVersion));
+                Manager manager = ctxt.getManager(); 
+                if (manager instanceof DistributedManager && showProxySessions) {
+                    args[5] = Integer.valueOf(
+                            ((DistributedManager)manager).getActiveSessionsFull());
+                } else if (ctxt.getManager() != null){
+                    args[5] = Integer.valueOf(manager.getActiveSessions());
+                } else {
+                    args[5] = Integer.valueOf(0);
+                }
+
+                args[6] = highlightColor;
+
+                writer.print
+                    (MessageFormat.format(APPS_ROW_DETAILS_SECTION, args));
+
+                args = new Object[14];
+                args[0] = RequestUtil.filter(response.encodeURL(request
+                        .getContextPath() + "/html/start?" + pathVersion));
+                args[1] = appsStart;
+                args[2] = RequestUtil.filter(response.encodeURL(request
+                        .getContextPath() + "/html/stop?" + pathVersion));
+                args[3] = appsStop;
+                args[4] = RequestUtil.filter(response.encodeURL(request
+                        .getContextPath() + "/html/reload?" + pathVersion));
+                args[5] = appsReload;
+                args[6] = RequestUtil.filter(response.encodeURL(request
+                        .getContextPath() + "/html/undeploy?" + pathVersion));
+                args[7] = appsUndeploy;
+                args[8] = RequestUtil.filter(response.encodeURL(request
+                        .getContextPath() + "/html/expire?" + pathVersion));
+                args[9] = appsExpire;
+                args[10] = smClient.getString(
+                        "htmlManagerServlet.expire.explain");
+                if (manager == null) {
+                    args[11] = smClient.getString(
+                            "htmlManagerServlet.noManager");
+                } else {
+                    args[11] = Integer.valueOf(
+                            ctxt.getManager().getMaxInactiveInterval()/60);
+                }
+                args[12] = smClient.getString("htmlManagerServlet.expire.unit");
+                args[13] = highlightColor;
+                
+                if (ctxt.getName().equals(this.context.getName())) {
+                    writer.print(MessageFormat.format(
+                        MANAGER_APP_ROW_BUTTON_SECTION, args));
+                } else if (ctxt.getAvailable() && isDeployed) {
+                    writer.print(MessageFormat.format(
+                        STARTED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args));
+                } else if (ctxt.getAvailable() && !isDeployed) {
+                    writer.print(MessageFormat.format(
+                        STARTED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args));
+                } else if (!ctxt.getAvailable() && isDeployed) {
+                    writer.print(MessageFormat.format(
+                        STOPPED_DEPLOYED_APPS_ROW_BUTTON_SECTION, args));
+                } else {
+                    writer.print(MessageFormat.format(
+                        STOPPED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION, args));
+                }
+
+            }
+        }
+
+        // Deploy Section
+        args = new Object[7];
+        args[0] = smClient.getString("htmlManagerServlet.deployTitle");
+        args[1] = smClient.getString("htmlManagerServlet.deployServer");
+        args[2] = response.encodeURL(request.getContextPath() + "/html/deploy");
+        args[3] = smClient.getString("htmlManagerServlet.deployPath");
+        args[4] = smClient.getString("htmlManagerServlet.deployConfig");
+        args[5] = smClient.getString("htmlManagerServlet.deployWar");
+        args[6] = smClient.getString("htmlManagerServlet.deployButton");
+        writer.print(MessageFormat.format(DEPLOY_SECTION, args));
+
+        args = new Object[4];
+        args[0] = smClient.getString("htmlManagerServlet.deployUpload");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/upload");
+        args[2] = smClient.getString("htmlManagerServlet.deployUploadFile");
+        args[3] = smClient.getString("htmlManagerServlet.deployButton");
+        writer.print(MessageFormat.format(UPLOAD_SECTION, args));
+
+        // Diagnostics section
+        args = new Object[5];
+        args[0] = smClient.getString("htmlManagerServlet.diagnosticsTitle");
+        args[1] = smClient.getString("htmlManagerServlet.diagnosticsLeak");
+        args[2] = response.encodeURL(
+                request.getContextPath() + "/html/findleaks");
+        args[3] = smClient.getString("htmlManagerServlet.diagnosticsLeakWarning");
+        args[4] = smClient.getString("htmlManagerServlet.diagnosticsLeakButton");
+        writer.print(MessageFormat.format(DIAGNOSTICS_SECTION, args));
+
+        // Server Header Section
+        args = new Object[7];
+        args[0] = smClient.getString("htmlManagerServlet.serverTitle");
+        args[1] = smClient.getString("htmlManagerServlet.serverVersion");
+        args[2] = smClient.getString("htmlManagerServlet.serverJVMVersion");
+        args[3] = smClient.getString("htmlManagerServlet.serverJVMVendor");
+        args[4] = smClient.getString("htmlManagerServlet.serverOSName");
+        args[5] = smClient.getString("htmlManagerServlet.serverOSVersion");
+        args[6] = smClient.getString("htmlManagerServlet.serverOSArch");
+        writer.print(MessageFormat.format
+                     (Constants.SERVER_HEADER_SECTION, args));
+
+        // Server Row Section
+        args = new Object[6];
+        args[0] = ServerInfo.getServerInfo();
+        args[1] = System.getProperty("java.runtime.version");
+        args[2] = System.getProperty("java.vm.vendor");
+        args[3] = System.getProperty("os.name");
+        args[4] = System.getProperty("os.version");
+        args[5] = System.getProperty("os.arch");
+        writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));
+
+        // HTML Tail Section
+        writer.print(Constants.HTML_TAIL_SECTION);
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+    }
+
+    /**
+     * Reload the web application at the specified context path.
+     *
+     * @see ManagerServlet#reload(PrintWriter, ContextName, StringManager)
+     *
+     * @param cn Name of the application to be restarted
+     * @param smClient  StringManager for the client's locale
+     * @return message String
+     */
+    protected String reload(ContextName cn, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.reload(printWriter, cn, smClient);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Undeploy the web application at the specified context path.
+     *
+     * @see ManagerServlet#undeploy(PrintWriter, ContextName, StringManager)
+     *
+     * @param cn Name of the application to be undeployed
+     * @param smClient  StringManager for the client's locale
+     * @return message String
+     */
+    protected String undeploy(ContextName cn, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.undeploy(printWriter, cn, smClient);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Display session information and invoke list.
+     *
+     * @see ManagerServlet#sessions(PrintWriter, ContextName, int,
+     *          StringManager)
+     *
+     * @param cn Name of the application to list session information
+     * @param idle Expire all sessions with idle time &ge; idle for this context
+     * @param smClient  StringManager for the client's locale
+     * @return message String
+     */
+    protected String sessions(ContextName cn, int idle, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.sessions(printWriter, cn, idle, smClient);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Start the web application at the specified context path.
+     *
+     * @see ManagerServlet#start(PrintWriter, ContextName, StringManager)
+     *
+     * @param cn Name of the application to be started
+     * @param smClient  StringManager for the client's locale
+     * @return message String
+     */
+    protected String start(ContextName cn, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.start(printWriter, cn, smClient);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Stop the web application at the specified context path.
+     *
+     * @see ManagerServlet#stop(PrintWriter, ContextName, StringManager)
+     *
+     * @param cn Name of the application to be stopped
+     * @param smClient  StringManager for the client's locale
+     * @return message String
+     */
+    protected String stop(ContextName cn, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.stop(printWriter, cn, smClient);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Find potential memory leaks caused by web application reload.
+     *
+     * @see ManagerServlet#findleaks(PrintWriter, StringManager) 
+     * 
+     * @param smClient  StringManager for the client's locale
+     *
+     * @return message String
+     */
+    protected String findleaks(StringManager smClient) {
+
+        StringBuilder msg = new StringBuilder();
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.findleaks(printWriter, smClient);
+
+        if (stringWriter.getBuffer().length() > 0) {
+            msg.append(smClient.getString("htmlManagerServlet.findleaksList"));
+            msg.append(stringWriter.toString());
+        } else {
+            msg.append(smClient.getString("htmlManagerServlet.findleaksNone"));
+        }
+        
+        return msg.toString();
+    }
+    
+    
+    /**
+     * @see javax.servlet.Servlet#getServletInfo()
+     */
+    @Override
+    public String getServletInfo() {
+        return "HTMLManagerServlet, Copyright (c) 1999-2011, The Apache Software Foundation";
+    }   
+    
+    /**
+     * @see javax.servlet.GenericServlet#init()
+     */
+    @Override
+    public void init() throws ServletException {
+        super.init();
+        
+        // Set our properties from the initialization parameters
+        String value = null;
+        value = getServletConfig().getInitParameter("showProxySessions");
+        showProxySessions = Boolean.parseBoolean(value);
+    }   
+
+    // ------------------------------------------------ Sessions administration
+
+    /**
+     *
+     * Extract the expiration request parameter
+     * 
+     * @param cn Name of the application from which to expire sessions
+     * @param req
+     * @param smClient  StringManager for the client's locale
+     */
+    protected String expireSessions(ContextName cn, HttpServletRequest req,
+            StringManager smClient) {
+        int idle = -1;
+        String idleParam = req.getParameter("idle");
+        if (idleParam != null) {
+            try {
+                idle = Integer.parseInt(idleParam);
+            } catch (NumberFormatException e) {
+                log("Could not parse idle parameter to an int: " + idleParam);
+            }
+        }
+        return sessions(cn, idle, smClient);
+    }
+
+    /**
+     * 
+     * @param req
+     * @param resp
+     * @param smClient  StringManager for the client's locale
+     * @throws ServletException
+     * @throws IOException 
+     */
+    protected void doSessions(ContextName cn, HttpServletRequest req,
+            HttpServletResponse resp, StringManager smClient)
+            throws ServletException, IOException {
+        req.setAttribute("path", cn.getPath());
+        req.setAttribute("version", cn.getVersion());
+        String action = req.getParameter("action");
+        if (debug >= 1) {
+            log("sessions: Session action '" + action +
+                    "' for web application '" + cn.getDisplayName() + "'");
+        }
+        if ("sessionDetail".equals(action)) {
+            String sessionId = req.getParameter("sessionId");
+            displaySessionDetailPage(req, resp, cn, sessionId, smClient);
+            return;
+        } else if ("invalidateSessions".equals(action)) {
+            String[] sessionIds = req.getParameterValues("sessionIds");
+            int i = invalidateSessions(cn, sessionIds, smClient);
+            req.setAttribute(APPLICATION_MESSAGE, "" + i + " sessions invalidated.");
+        } else if ("removeSessionAttribute".equals(action)) {
+            String sessionId = req.getParameter("sessionId");
+            String name = req.getParameter("attributeName");
+            boolean removed =
+                removeSessionAttribute(cn, sessionId, name, smClient);
+            String outMessage = removed ? "Session attribute '" + name + "' removed." : "Session did not contain any attribute named '" + name + "'";
+            req.setAttribute(APPLICATION_MESSAGE, outMessage);
+            displaySessionDetailPage(req, resp, cn, sessionId, smClient);
+            return;
+        } // else
+        displaySessionsListPage(cn, req, resp, smClient);
+    }
+
+    protected List<Session> getSessionsForName(ContextName cn,
+            StringManager smClient) {
+        if ((cn == null) || !(cn.getPath().startsWith("/") ||
+                cn.getPath().equals(""))) {
+            String path = null;
+            if (cn != null) {
+                path = cn.getPath();
+            }
+            throw new IllegalArgumentException(smClient.getString(
+                    "managerServlet.invalidPath",
+                    RequestUtil.filter(path)));
+        }
+
+        Context ctxt = (Context) host.findChild(cn.getName());
+        if (null == ctxt) {
+            throw new IllegalArgumentException(smClient.getString(
+                    "managerServlet.noContext",
+                    RequestUtil.filter(cn.getDisplayName())));
+        }
+        Manager manager = ctxt.getManager();
+        List<Session> sessions = new ArrayList<Session>();
+        sessions.addAll(Arrays.asList(manager.findSessions()));
+        if (manager instanceof DistributedManager && showProxySessions) {
+            // Add dummy proxy sessions
+            Set<String> sessionIds =
+                ((DistributedManager) manager).getSessionIdsFull();
+            // Remove active (primary and backup) session IDs from full list
+            for (Session session : sessions) {
+                sessionIds.remove(session.getId());
+            }
+            // Left with just proxy sessions - add them
+            for (String sessionId : sessionIds) {
+                sessions.add(new DummyProxySession(sessionId));
+            }
+        }
+        return sessions;
+    }
+    protected Session getSessionForNameAndId(ContextName cn, String id,
+            StringManager smClient) throws IOException {
+        if ((cn == null) || !(cn.getPath().startsWith("/") ||
+                cn.getPath().equals(""))) {
+            String path = null;
+            if (cn != null) {
+                path = cn.getPath();
+            }
+            throw new IllegalArgumentException(smClient.getString(
+                    "managerServlet.invalidPath",
+                    RequestUtil.filter(path)));
+        }
+        
+        Context ctxt = (Context) host.findChild(cn.getName());
+        if (null == ctxt) {
+            throw new IllegalArgumentException(smClient.getString(
+                    "managerServlet.noContext",
+                    RequestUtil.filter(cn.getDisplayName())));
+        }
+        Session session = ctxt.getManager().findSession(id);
+        return session;
+    }
+
+    /**
+     * 
+     * @param cn Name of the application for which the sessions will be listed
+     * @param req
+     * @param resp
+     * @param smClient  StringManager for the client's locale
+     * @throws ServletException
+     * @throws IOException
+     */
+    protected void displaySessionsListPage(ContextName cn,
+            HttpServletRequest req, HttpServletResponse resp,
+            StringManager smClient)
+            throws ServletException, IOException {
+        List<Session> sessions = getSessionsForName(cn, smClient);
+        String sortBy = req.getParameter("sort");
+        String orderBy = null;
+        if (null != sortBy && !"".equals(sortBy.trim())) {
+            Comparator<Session> comparator = getComparator(sortBy);
+            if (comparator != null) {
+                orderBy = req.getParameter("order");
+                if ("DESC".equalsIgnoreCase(orderBy)) {
+                    comparator = new ReverseComparator(comparator);
+                    orderBy = "ASC";
+                } else {
+                    orderBy = "DESC";
+                }
+                try {
+                    Collections.sort(sessions, comparator);
+                } catch (IllegalStateException ise) {
+                    // at least 1 of the sessions is invalidated
+                    req.setAttribute(APPLICATION_ERROR, "Can't sort session list: one session is invalidated");
+                }
+            } else {
+                log("WARNING: unknown sort order: " + sortBy);
+            }
+        }
+        // keep sort order
+        req.setAttribute("sort", sortBy);
+        req.setAttribute("order", orderBy);
+        req.setAttribute("activeSessions", sessions);
+        //strong>NOTE</strong> - This header will be overridden
+        // automatically if a <code>RequestDispatcher.forward()</code> call is
+        // ultimately invoked.
+        resp.setHeader("Pragma", "No-cache"); // HTTP 1.0
+        resp.setHeader("Cache-Control", "no-cache,no-store,max-age=0"); // HTTP 1.1
+        resp.setDateHeader("Expires", 0); // 0 means now
+        getServletContext().getRequestDispatcher(sessionsListJspPath).include(req, resp);
+    }
+
+    /**
+     * 
+     * @param req
+     * @param resp
+     * @param smClient  StringManager for the client's locale
+     * @throws ServletException
+     * @throws IOException
+     */
+    protected void displaySessionDetailPage(HttpServletRequest req,
+            HttpServletResponse resp, ContextName cn, String sessionId,
+            StringManager smClient) throws ServletException, IOException {
+        Session session = getSessionForNameAndId(cn, sessionId, smClient);
+        //strong>NOTE</strong> - This header will be overridden
+        // automatically if a <code>RequestDispatcher.forward()</code> call is
+        // ultimately invoked.
+        resp.setHeader("Pragma", "No-cache"); // HTTP 1.0
+        resp.setHeader("Cache-Control", "no-cache,no-store,max-age=0"); // HTTP 1.1
+        resp.setDateHeader("Expires", 0); // 0 means now
+        req.setAttribute("currentSession", session);
+        getServletContext().getRequestDispatcher(resp.encodeURL(sessionDetailJspPath)).include(req, resp);
+    }
+
+    /**
+     * Invalidate HttpSessions
+     * @param cn Name of the application for which sessions are to be
+     *           invalidated
+     * @param sessionIds
+     * @param smClient  StringManager for the client's locale
+     * @return number of invalidated sessions
+     * @throws IOException 
+     */
+    protected int invalidateSessions(ContextName cn, String[] sessionIds,
+            StringManager smClient) throws IOException {
+        if (null == sessionIds) {
+            return 0;
+        }
+        int nbAffectedSessions = 0;
+        for (int i = 0; i < sessionIds.length; ++i) {
+            String sessionId = sessionIds[i];
+            HttpSession session =
+                getSessionForNameAndId(cn, sessionId, smClient).getSession();
+            if (null == session) {
+                // Shouldn't happen, but let's play nice...
+                if (debug >= 1) {
+                    log("WARNING: can't invalidate null session " + sessionId);
+                }
+                continue;
+            }
+            try {
+                session.invalidate();
+                ++nbAffectedSessions;
+                if (debug >= 1) {
+                    log("Invalidating session id " + sessionId);
+                }
+            } catch (IllegalStateException ise) {
+                if (debug >= 1) {
+                    log("Can't invalidate already invalidated session id " + sessionId);
+                }
+            }
+        }
+        return nbAffectedSessions;
+    }
+
+    /**
+     * Removes an attribute from an HttpSession
+     * @param cn Name of the application hosting the session from which the
+     *           attribute is to be removed
+     * @param sessionId
+     * @param attributeName
+     * @param smClient  StringManager for the client's locale
+     * @return true if there was an attribute removed, false otherwise
+     * @throws IOException 
+     */
+    protected boolean removeSessionAttribute(ContextName cn, String sessionId,
+            String attributeName, StringManager smClient) throws IOException {
+        HttpSession session =
+            getSessionForNameAndId(cn, sessionId, smClient).getSession();
+        if (null == session) {
+            // Shouldn't happen, but let's play nice...
+            if (debug >= 1) {
+                log("WARNING: can't remove attribute '" + attributeName + "' for null session " + sessionId);
+            }
+            return false;
+        }
+        boolean wasPresent = (null != session.getAttribute(attributeName));
+        try {
+            session.removeAttribute(attributeName);
+        } catch (IllegalStateException ise) {
+            if (debug >= 1) {
+                log("Can't remote attribute '" + attributeName + "' for invalidated session id " + sessionId);
+            }
+        }
+        return wasPresent;
+    }
+
+    protected Comparator<Session> getComparator(String sortBy) {
+        Comparator<Session> comparator = null;
+        if ("CreationTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<Date>() {
+                @Override
+                public Comparable<Date> getComparableObject(Session session) {
+                    return new Date(session.getCreationTime());
+                }
+            };
+        } else if ("id".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<String>() {
+                @Override
+                public Comparable<String> getComparableObject(Session session) {
+                    return session.getId();
+                }
+            };
+        } else if ("LastAccessedTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<Date>() {
+                @Override
+                public Comparable<Date> getComparableObject(Session session) {
+                    return new Date(session.getLastAccessedTime());
+                }
+            };
+        } else if ("MaxInactiveInterval".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<Date>() {
+                @Override
+                public Comparable<Date> getComparableObject(Session session) {
+                    return new Date(session.getMaxInactiveInterval());
+                }
+            };
+        } else if ("new".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<Boolean>() {
+                @Override
+                public Comparable<Boolean> getComparableObject(Session session) {
+                    return Boolean.valueOf(session.getSession().isNew());
+                }
+            };
+        } else if ("locale".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<String>() {
+                @Override
+                public Comparable<String> getComparableObject(Session session) {
+                    return JspHelper.guessDisplayLocaleFromSession(session);
+                }
+            };
+        } else if ("user".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<String>() {
+                @Override
+                public Comparable<String> getComparableObject(Session session) {
+                    return JspHelper.guessDisplayUserFromSession(session);
+                }
+            };
+        } else if ("UsedTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<Date>() {
+                @Override
+                public Comparable<Date> getComparableObject(Session session) {
+                    return new Date(SessionUtils.getUsedTimeForSession(session));
+                }
+            };
+        } else if ("InactiveTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<Date>() {
+                @Override
+                public Comparable<Date> getComparableObject(Session session) {
+                    return new Date(SessionUtils.getInactiveTimeForSession(session));
+                }
+            };
+        } else if ("TTL".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator<Date>() {
+                @Override
+                public Comparable<Date> getComparableObject(Session session) {
+                    return new Date(SessionUtils.getTTLForSession(session));
+                }
+            };
+        }
+        //TODO: complete this to TTL, etc.
+        return comparator;
+    }
+
+    // ------------------------------------------------------ Private Constants
+
+    // These HTML sections are broken in relatively small sections, because of
+    // limited number of substitutions MessageFormat can process
+    // (maximum of 10).
+
+    private static final String APPS_HEADER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"6\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"header-left\"><small>{1}</small></td>\n" +
+        " <td class=\"header-left\"><small>{2}</small></td>\n" +
+        " <td class=\"header-center\"><small>{3}</small></td>\n" +
+        " <td class=\"header-center\"><small>{4}</small></td>\n" +
+        " <td class=\"header-left\"><small>{5}</small></td>\n" +
+        " <td class=\"header-left\"><small>{6}</small></td>\n" +
+        "</tr>\n";
+
+    private static final String APPS_ROW_DETAILS_SECTION =
+        "<tr>\n" +
+        " <td class=\"row-left\" bgcolor=\"{6}\" rowspan=\"2\"><small>{0}</small></td>\n" +
+        " <td class=\"row-left\" bgcolor=\"{6}\" rowspan=\"2\"><small>{1}</small></td>\n" +
+        " <td class=\"row-left\" bgcolor=\"{6}\" rowspan=\"2\"><small>{2}</small></td>\n" +
+        " <td class=\"row-center\" bgcolor=\"{6}\" rowspan=\"2\"><small>{3}</small></td>\n" +
+        " <td class=\"row-center\" bgcolor=\"{6}\" rowspan=\"2\">" +
+        "<small><a href=\"{4}\">{5}</a></small></td>\n";
+
+    private static final String MANAGER_APP_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{13}\">\n" +
+        "  <small>\n" +
+        "  &nbsp;{1}&nbsp;\n" +
+        "  &nbsp;{3}&nbsp;\n" +
+        "  &nbsp;{5}&nbsp;\n" +
+        "  &nbsp;{7}&nbsp;\n" +
+        "  </small>\n" +
+        " </td>\n" +
+        "</tr><tr>\n" +
+        " <td class=\"row-left\" bgcolor=\"{13}\">\n" +
+        "  <form method=\"POST\" action=\"{8}\">\n" +
+        "  <small>\n" +
+        "  &nbsp;<input type=\"submit\" value=\"{9}\">&nbsp;{10}&nbsp;<input type=\"text\" name=\"idle\" size=\"5\" value=\"{11}\">&nbsp;{12}&nbsp;\n" +
+        "  </small>\n" +
+        "  </form>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String STARTED_DEPLOYED_APPS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{13}\">\n" +
+        "  &nbsp;<small>{1}</small>&nbsp;\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{2}\">" +
+        "  <small><input type=\"submit\" value=\"{3}\"></small>" +
+        "  </form>\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{4}\">" +
+        "  <small><input type=\"submit\" value=\"{5}\"></small>" +
+        "  </form>\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{6}\">" +
+        "  <small><input type=\"submit\" value=\"{7}\"></small>" +
+        "  </form>\n" +
+        " </td>\n" +
+        " </tr><tr>\n" +
+        " <td class=\"row-left\" bgcolor=\"{13}\">\n" +
+        "  <form method=\"POST\" action=\"{8}\">\n" +
+        "  <small>\n" +
+        "  &nbsp;<input type=\"submit\" value=\"{9}\">&nbsp;{10}&nbsp;<input type=\"text\" name=\"idle\" size=\"5\" value=\"{11}\">&nbsp;{12}&nbsp;\n" +
+        "  </small>\n" +
+        "  </form>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String STOPPED_DEPLOYED_APPS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{13}\" rowspan=\"2\">\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{0}\">" +
+        "  <small><input type=\"submit\" value=\"{1}\"></small>" +
+        "  </form>\n" +
+        "  &nbsp;<small>{3}</small>&nbsp;\n" +
+        "  &nbsp;<small>{5}</small>&nbsp;\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{6}\">" +
+        "  <small><input type=\"submit\" value=\"{7}\"></small>" +
+        "  </form>\n" +
+        " </td>\n" +
+        "</tr>\n<tr></tr>\n";
+
+    private static final String STARTED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{13}\" rowspan=\"2\">\n" +
+        "  &nbsp;<small>{1}</small>&nbsp;\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{2}\">" +
+        "  <small><input type=\"submit\" value=\"{3}\"></small>" +
+        "  </form>\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{4}\">" +
+        "  <small><input type=\"submit\" value=\"{5}\"></small>" +
+        "  </form>\n" +
+        "  &nbsp;<small>{7}</small>&nbsp;\n" +
+        " </td>\n" +
+        "</tr>\n<tr></tr>\n";
+
+    private static final String STOPPED_NONDEPLOYED_APPS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{13}\" rowspan=\"2\">\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{0}\">" +
+        "  <small><input type=\"submit\" value=\"{1}\"></small>" +
+        "  </form>\n" +
+        "  &nbsp;<small>{3}</small>&nbsp;\n" +
+        "  &nbsp;<small>{5}</small>&nbsp;\n" +
+        "  &nbsp;<small>{7}</small>&nbsp;\n" +
+        " </td>\n" +
+        "</tr>\n<tr></tr>\n";
+
+    private static final String DEPLOY_SECTION =
+        "</table>\n" +
+        "<br>\n" +
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"header-left\"><small>{1}</small></td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\">\n" +
+        "<form method=\"post\" action=\"{2}\">\n" +
+        "<table cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{3}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"deployPath\" size=\"20\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{4}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"deployConfig\" size=\"20\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{5}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"deployWar\" size=\"40\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  &nbsp;\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"submit\" value=\"{6}\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "</form>\n" +
+        "</td>\n" +
+        "</tr>\n";
+
+    private static final String UPLOAD_SECTION =
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"header-left\"><small>{0}</small></td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\">\n" +
+        "<form method=\"post\" action=\"{1}\" " +
+        "enctype=\"multipart/form-data\">\n" +
+        "<table cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{2}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"file\" name=\"deployWar\" size=\"40\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  &nbsp;\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"submit\" value=\"{3}\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "</form>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    private static final String DIAGNOSTICS_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"header-left\"><small>{1}</small></td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\">\n" +
+        "<form method=\"post\" action=\"{2}\">\n" +
+        "<table cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"submit\" value=\"{4}\">\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <small>{3}</small>\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "</form>\n" +
+        "</td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "<br>";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/JMXProxyServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/JMXProxyServlet.java
new file mode 100644
index 0000000..57bed98
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/JMXProxyServlet.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.manager;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * This servlet will dump JMX attributes in a simple format
+ * and implement proxy services for modeler.
+ *
+ * @author Costin Manolache
+ */
+public class JMXProxyServlet extends HttpServlet  {
+    
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------- Instance Variables
+    /**
+     * MBean server.
+     */
+    protected transient MBeanServer mBeanServer = null;
+    protected transient Registry registry;
+
+    // --------------------------------------------------------- Public Methods
+    /**
+     * Initialize this servlet.
+     */
+    @Override
+    public void init() throws ServletException {
+        // Retrieve the MBean server
+        registry = Registry.getRegistry(null, null);
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException
+    {
+
+        response.setContentType("text/plain");
+
+        PrintWriter writer = response.getWriter();
+
+        if( mBeanServer==null ) {
+            writer.println("Error - No mbean server");
+            return;
+        }
+
+        String qry=request.getParameter("set");
+        if( qry!= null ) {
+            String name=request.getParameter("att");
+            String val=request.getParameter("val");
+
+            setAttribute( writer, qry, name, val );
+            return;
+        }
+        qry=request.getParameter("get");
+        if( qry!= null ) {
+            String name=request.getParameter("att");
+            getAttribute( writer, qry, name );
+            return;
+        }        
+        qry=request.getParameter("qry");
+        if( qry == null ) {
+            qry = "*:*";
+        }
+
+        listBeans( writer, qry );
+
+    }
+
+    public void getAttribute(PrintWriter writer, String onameStr, String att) {
+        try {
+            ObjectName oname = new ObjectName(onameStr);
+            Object value = mBeanServer.getAttribute(oname, att);
+            writer.println("OK - Attribute get '" + onameStr + "' - " + att
+                    + "= " + escape(value.toString()));
+        } catch (Exception ex) {
+            writer.println("Error - " + ex.toString());
+        }
+    }
+
+    public void setAttribute( PrintWriter writer,
+                              String onameStr, String att, String val )
+    {
+        try {
+            ObjectName oname=new ObjectName( onameStr );
+            String type=registry.getType(oname, att);
+            Object valueObj=registry.convertValue(type, val );
+            mBeanServer.setAttribute( oname, new Attribute(att, valueObj));
+            writer.println("OK - Attribute set");
+        } catch( Exception ex ) {
+            writer.println("Error - " + ex.toString());
+        }
+    }
+
+    public void listBeans( PrintWriter writer, String qry )
+    {
+
+        Set<ObjectName> names = null;
+        try {
+            names=mBeanServer.queryNames(new ObjectName(qry), null);
+            writer.println("OK - Number of results: " + names.size());
+            writer.println();
+        } catch (Exception e) {
+            writer.println("Error - " + e.toString());
+            return;
+        }
+
+        Iterator<ObjectName> it=names.iterator();
+        while( it.hasNext()) {
+            ObjectName oname=it.next();
+            writer.println( "Name: " + oname.toString());
+
+            try {
+                MBeanInfo minfo=mBeanServer.getMBeanInfo(oname);
+                // can't be null - I think
+                String code=minfo.getClassName();
+                if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) {
+                    code=(String)mBeanServer.getAttribute(oname, "modelerType");
+                }
+                writer.println("modelerType: " + code);
+
+                MBeanAttributeInfo attrs[]=minfo.getAttributes();
+                Object value=null;
+
+                for( int i=0; i< attrs.length; i++ ) {
+                    if( ! attrs[i].isReadable() ) continue;
+                    if( ! isSupported( attrs[i].getType() )) continue;
+                    String attName=attrs[i].getName();
+                    if( "modelerType".equals( attName)) continue;
+                    if( attName.indexOf( "=") >=0 ||
+                            attName.indexOf( ":") >=0 ||
+                            attName.indexOf( " ") >=0 ) {
+                        continue;
+                    }
+            
+                    try {
+                        value=mBeanServer.getAttribute(oname, attName);
+                    } catch( Throwable t) {
+                        log("Error getting attribute " + oname +
+                            " " + attName + " " + t.toString());
+                        continue;
+                    }
+                    if( value==null ) continue;
+                    String valueString;
+                    try {
+                        Class<?> c = value.getClass();
+                        if (c.isArray()) {
+                            int len = Array.getLength(value);
+                            StringBuilder sb = new StringBuilder("Array[" +
+                                    c.getComponentType().getName() + "] of length " + len);
+                            if (len > 0) {
+                                sb.append("\r\n");
+                            }
+                            for (int j = 0; j < len; j++) {
+                                sb.append("\t");
+                                Object item = Array.get(value, j);
+                                if (item == null) {
+                                    sb.append("NULL VALUE");
+                                } else {
+                                    try {
+                                        sb.append(escape(item.toString()));
+                                    }
+                                    catch (Throwable t) {
+                                        ExceptionUtils.handleThrowable(t);
+                                        sb.append("NON-STRINGABLE VALUE");
+                                    }
+                                }
+                                if (j < len - 1) {
+                                    sb.append("\r\n");
+                                }
+                            }
+                            valueString = sb.toString();
+                        }
+                        else {
+                            valueString = escape(value.toString());
+                        }
+                        writer.println( attName + ": " + valueString);
+                    }
+                    catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+            }
+            writer.println();
+        }
+
+    }
+
+    public String escape(String value) {
+        // The only invalid char is \n
+        // We also need to keep the string short and split it with \nSPACE
+        // XXX TODO
+        int idx=value.indexOf( "\n" );
+        if( idx < 0 ) return value;
+
+        int prev=0;
+        StringBuilder sb=new StringBuilder();
+        while( idx >= 0 ) {
+            appendHead(sb, value, prev, idx);
+
+            sb.append( "\\n\n ");
+            prev=idx+1;
+            if( idx==value.length() -1 ) break;
+            idx=value.indexOf('\n', idx+1);
+        }
+        if( prev < value.length() )
+            appendHead( sb, value, prev, value.length());
+        return sb.toString();
+    }
+
+    private void appendHead( StringBuilder sb, String value, int start, int end) {
+        if (end < 1) return;
+
+        int pos=start;
+        while( end-pos > 78 ) {
+            sb.append( value.substring(pos, pos+78));
+            sb.append( "\n ");
+            pos=pos+78;
+        }
+        sb.append( value.substring(pos,end));
+    }
+
+    /**
+     * Determines if a type is supported by the {@link JMXProxyServlet}.
+     * 
+     * @param type  The type to check
+     * @return      Always returns <code>true</code>
+     */
+    public boolean isSupported(String type) {
+        return true;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/JspHelper.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/JspHelper.java
new file mode 100644
index 0000000..5da2ea3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/JspHelper.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager;
+
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.manager.util.SessionUtils;
+
+
+/**
+ * Helper JavaBean for JSPs, because JSTL 1.1/EL 2.0 is too dumb to
+ * to what I need (call methods with parameters), or I am too dumb to use it correctly. :)
+ * @author C&eacute;drik LIME
+ */
+public class JspHelper {
+
+    private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * Public constructor, so that this class can be considered a JavaBean
+     */
+    private JspHelper() {
+        super();
+    }
+
+    /**
+     * Try to get user locale from the session, if possible.
+     * IMPLEMENTATION NOTE: this method has explicit support for Tapestry 3 and Struts 1.x
+     * @param in_session
+     * @return String
+     */
+    public static String guessDisplayLocaleFromSession(Session in_session) {
+        return localeToString(SessionUtils.guessLocaleFromSession(in_session));
+    }
+    private static String localeToString(Locale locale) {
+        if (locale != null) {
+            return escapeXml(locale.toString());//locale.getDisplayName();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Try to get user name from the session, if possible.
+     * @param in_session
+     * @return String
+     */
+    public static String guessDisplayUserFromSession(Session in_session) {
+        Object user = SessionUtils.guessUserFromSession(in_session);
+        return escapeXml(user);
+    }
+
+
+    public static String getDisplayCreationTimeForSession(Session in_session) {
+        try {
+            if (in_session.getCreationTime() == 0) {
+                return "";
+            }
+            DateFormat formatter = new SimpleDateFormat(DATE_TIME_FORMAT);
+            return formatter.format(new Date(in_session.getCreationTime()));
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return "";
+        }
+    }
+
+    public static String getDisplayLastAccessedTimeForSession(Session in_session) {
+        try {
+            if (in_session.getLastAccessedTime() == 0) {
+                return "";
+            }
+            DateFormat formatter = new SimpleDateFormat(DATE_TIME_FORMAT);
+            return formatter.format(new Date(in_session.getLastAccessedTime()));
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return "";
+        }
+    }
+
+    public static String getDisplayUsedTimeForSession(Session in_session) {
+        try {
+            if (in_session.getCreationTime() == 0) {
+                return "";
+            }
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return "";
+        }
+        return secondsToTimeString(SessionUtils.getUsedTimeForSession(in_session)/1000);
+    }
+
+    public static String getDisplayTTLForSession(Session in_session) {
+        try {
+            if (in_session.getCreationTime() == 0) {
+                return "";
+            }
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return "";
+        }
+        return secondsToTimeString(SessionUtils.getTTLForSession(in_session)/1000);
+    }
+
+    public static String getDisplayInactiveTimeForSession(Session in_session) {
+        try {
+            if (in_session.getCreationTime() == 0) {
+                return "";
+            }
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return "";
+        }
+        return secondsToTimeString(SessionUtils.getInactiveTimeForSession(in_session)/1000);
+    }
+
+    public static String secondsToTimeString(long in_seconds) {
+        StringBuilder buff = new StringBuilder(9);
+        if (in_seconds < 0) {
+            buff.append('-');
+            in_seconds = -in_seconds;
+        }
+        long rest = in_seconds;
+        long hour = rest / 3600;
+        rest = rest % 3600;
+        long minute = rest / 60;
+        rest = rest % 60;
+        long second = rest;
+        if (hour < 10) {
+            buff.append('0');
+        }
+        buff.append(hour);
+        buff.append(':');
+        if (minute < 10) {
+            buff.append('0');
+        }
+        buff.append(minute);
+        buff.append(':');
+        if (second < 10) {
+            buff.append('0');
+        }
+        buff.append(second);
+        return buff.toString();
+    }
+
+
+    /*
+     * Following copied from org.apache.taglibs.standard.tag.common.core.Util v1.1.2
+     */
+
+    private static final int HIGHEST_SPECIAL = '>';
+    private static char[][] specialCharactersRepresentation = new char[HIGHEST_SPECIAL + 1][];
+    static {
+        specialCharactersRepresentation['&'] = "&amp;".toCharArray();
+        specialCharactersRepresentation['<'] = "&lt;".toCharArray();
+        specialCharactersRepresentation['>'] = "&gt;".toCharArray();
+        specialCharactersRepresentation['"'] = "&#034;".toCharArray();
+        specialCharactersRepresentation['\''] = "&#039;".toCharArray();
+    }
+
+    public static String escapeXml(Object obj) {
+        String value = null;
+        try {
+            value = (obj == null) ? null : obj.toString();
+        } catch (Exception e) {
+            // Ignore
+        }
+        return escapeXml(value);
+    }
+
+    /**
+     * Performs the following substring replacements
+     * (to facilitate output to XML/HTML pages):
+     *
+     *    & -> &amp;
+     *    < -> &lt;
+     *    > -> &gt;
+     *    " -> &#034;
+     *    ' -> &#039;
+     *
+     * See also OutSupport.writeEscapedXml().
+     */
+    @SuppressWarnings("null") // escapedBuffer cannot be null
+    public static String escapeXml(String buffer) {
+        if (buffer == null) {
+            return "";
+        }
+        int start = 0;
+        int length = buffer.length();
+        char[] arrayBuffer = buffer.toCharArray();
+        StringBuilder escapedBuffer = null;
+
+        for (int i = 0; i < length; i++) {
+            char c = arrayBuffer[i];
+            if (c <= HIGHEST_SPECIAL) {
+                char[] escaped = specialCharactersRepresentation[c];
+                if (escaped != null) {
+                    // create StringBuilder to hold escaped xml string
+                    if (start == 0) {
+                        escapedBuffer = new StringBuilder(length + 5);
+                    }
+                    // add unescaped portion
+                    if (start < i) {
+                        escapedBuffer.append(arrayBuffer,start,i-start);
+                    }
+                    start = i + 1;
+                    // add escaped xml
+                    escapedBuffer.append(escaped);
+                }
+            }
+        }
+        // no xml escaping was necessary
+        if (start == 0) {
+            return buffer;
+        }
+        // add rest of unescaped portion
+        if (start < length) {
+            escapedBuffer.append(arrayBuffer,start,length-start);
+        }
+        return escapedBuffer.toString();
+    }
+
+    public static String formatNumber(long number) {
+        return NumberFormat.getNumberInstance().format(number);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings.properties
new file mode 100644
index 0000000..8e1f50a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings.properties
@@ -0,0 +1,118 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+htmlManagerServlet.appsAvailable=Running
+htmlManagerServlet.appsName=Display Name
+htmlManagerServlet.appsPath=Path
+htmlManagerServlet.appsReload=Reload
+htmlManagerServlet.appsUndeploy=Undeploy
+htmlManagerServlet.appsVersion=Version
+htmlManagerServlet.appsExpire=Expire sessions
+htmlManagerServlet.appsSessions=Sessions
+htmlManagerServlet.appsStart=Start
+htmlManagerServlet.appsStop=Stop
+htmlManagerServlet.appsTasks=Commands
+htmlManagerServlet.appsTitle=Applications
+htmlManagerServlet.noVersion=None specified
+htmlManagerServlet.expire.explain=with idle &ge;
+htmlManagerServlet.expire.unit=minutes
+htmlManagerServlet.helpHtmlManager=HTML Manager Help
+htmlManagerServlet.helpHtmlManagerFile=../docs/html-manager-howto.html
+htmlManagerServlet.helpManager=Manager Help
+htmlManagerServlet.helpManagerFile=../docs/manager-howto.html
+htmlManagerServlet.deployButton=Deploy
+htmlManagerServlet.deployConfig=XML Configuration file URL:
+htmlManagerServlet.deployPath=Context Path (required):
+htmlManagerServlet.deployServer=Deploy directory or WAR file located on server
+htmlManagerServlet.deployTitle=Deploy
+htmlManagerServlet.deployUpload=WAR file to deploy
+htmlManagerServlet.deployUploadFail=FAIL - Deploy Upload Failed, Exception: {0}
+htmlManagerServlet.deployUploadFile=Select WAR file to upload
+htmlManagerServlet.deployUploadInServerXml=FAIL - War file \"{0}\" cannot be uploaded if context is defined in server.xml 
+htmlManagerServlet.deployUploadNotWar=FAIL - File uploaded \"{0}\" must be a .war
+htmlManagerServlet.deployUploadNoFile=FAIL - File upload failed, no file
+htmlManagerServlet.deployUploadWarExists=FAIL - War file \"{0}\" already exists on server
+htmlManagerServlet.deployWar=WAR or Directory URL:
+htmlManagerServlet.diagnosticsLeak=Check to see if a web application has caused a memory leak on stop, reload or undeploy
+htmlManagerServlet.diagnosticsLeakButton=Find leaks
+htmlManagerServlet.diagnosticsLeakWarning=This diagnostic check will trigger a full garbage collection. Use it with extreme caution on production systems.
+htmlManagerServlet.diagnosticsTitle=Diagnostics
+htmlManagerServlet.findleaksList=\
+  The following web applications were stopped (reloaded, undeployed), but their\n\
+  classes from previous runs are still loaded in memory, thus causing a memory\n\
+  leak (use a profiler to confirm):\n
+htmlManagerServlet.findleaksNone=No web applications appear to have triggered a memory leak on stop, reload or undeploy.
+htmlManagerServlet.list=List Applications
+htmlManagerServlet.manager=Manager
+htmlManagerServlet.messageLabel=Message:
+htmlManagerServlet.noManager=-
+htmlManagerServlet.serverJVMVendor=JVM Vendor
+htmlManagerServlet.serverJVMVersion=JVM Version
+htmlManagerServlet.serverOSArch=OS Architecture
+htmlManagerServlet.serverOSName=OS Name
+htmlManagerServlet.serverOSVersion=OS Version
+htmlManagerServlet.serverTitle=Server Information
+htmlManagerServlet.serverVersion=Tomcat Version
+htmlManagerServlet.title=Tomcat Web Application Manager
+managerServlet.alreadyContext=FAIL - Application already exists at path {0}
+managerServlet.alreadyDocBase=FAIL - Directory {0} is already in use
+managerServlet.configured=OK - Deployed application from context file {0}
+managerServlet.deleteFail=FAIL - Unable to delete [{0}]. The continued presence of this file may cause problems.
+managerServlet.deployed=OK - Deployed application at context path {0}
+managerServlet.deployFailed=FAIL - Failed to deploy application at context path {0}
+managerServlet.deployedButNotStarted=FAIL - Deployed application at context path {0} but context failed to start
+managerServlet.exception=FAIL - Encountered exception {0}
+managerServlet.findleaksFail=FAIL - Find leaks failed: Host not instance of StandardHost
+managerServlet.invalidPath=FAIL - Invalid context path {0} was specified
+managerServlet.invalidWar=FAIL - Invalid application URL {0} was specified
+managerServlet.listed=OK - Listed applications for virtual host {0}
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.mkdirFail=FAIL - Unable to create directory [{0}]
+managerServlet.noAppBase=FAIL - Cannot identify application base for context path {0}
+managerServlet.noCommand=FAIL - No command was specified
+managerServlet.noContext=FAIL - No context exists for path {0}
+managerServlet.noDirectory=FAIL - Non-directory document base for path {0}
+managerServlet.noDocBase=FAIL - Cannot undeploy document base for path {0}
+managerServlet.noGlobal=FAIL - No global JNDI resources are available
+managerServlet.noManager=FAIL - No manager exists for path {0}
+managerServlet.noReload=FAIL - Reload not supported on WAR deployed at path {0}
+managerServlet.noRename=FAIL - Cannot deploy uploaded WAR for path {0}
+managerServlet.noRole=FAIL - User does not possess role {0}
+managerServlet.noSelf=FAIL - The manager can not reload, undeploy, stop, or undeploy itself
+managerServlet.noWrapper=Container has not called setWrapper() for this servlet
+managerServlet.notDeployed=FAIL - Context {0} is defined in server.xml and may not be undeployed
+managerServlet.objectNameFail=FAIL - Unable to register object name [{0}] for Manager Servlet
+managerServlet.postCommand=FAIL - Tried to use command {0} via a GET request but POST is required
+managerServlet.reloaded=OK - Reloaded application at context path {0}
+managerServlet.resourcesAll=OK - Listed global resources of all types
+managerServlet.resourcesType=OK - Listed global resources of type {0}
+managerServlet.saveFail=FAIL - Configuration save failed: {0}
+managerServlet.saved=OK - Server configuration saved
+managerServlet.savedContext=OK - Context {0} configuration saved
+managerServlet.sessiondefaultmax=Default maximum session inactive interval {0} minutes
+managerServlet.sessiontimeout={0} minutes: {1} sessions
+managerServlet.sessiontimeout.unlimited=unlimited time: {0} sessions
+managerServlet.sessiontimeout.expired={0} minutes: {1} sessions were expired
+managerServlet.sessions=OK - Session information for application at context path {0}
+managerServlet.started=OK - Started application at context path {0}
+managerServlet.startFailed=FAIL - Application at context path {0} could not be started
+managerServlet.stopped=OK - Stopped application at context path {0}
+managerServlet.undeployed=OK - Undeployed application at context path {0}
+managerServlet.unknownCommand=FAIL - Unknown command {0}
+managerServlet.userDatabaseError=FAIL - Cannot resolve user database reference
+managerServlet.userDatabaseMissing=FAIL - No user database is available
+
+statusServlet.title=Server Status
+statusServlet.complete=Complete Server Status
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_de.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_de.properties
new file mode 100644
index 0000000..f9082e8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_de.properties
@@ -0,0 +1,95 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+htmlManagerServlet.appsAvailable=Verf\u00fcgbar
+htmlManagerServlet.appsName=Anzeigename
+htmlManagerServlet.appsPath=Kontext Pfad
+htmlManagerServlet.appsReload=Neu laden
+htmlManagerServlet.appsUndeploy=Entfernen
+htmlManagerServlet.appsExpire=L\u00f6sche Sitzungen
+htmlManagerServlet.appsSessions=Sitzungen
+htmlManagerServlet.appsStart=Start
+htmlManagerServlet.appsStop=Stop
+htmlManagerServlet.appsTasks=Kommandos
+htmlManagerServlet.appsTitle=Anwendungen
+htmlManagerServlet.expire.explain=mit Inaktivit\u00e4t &ge;
+htmlManagerServlet.expire.unit=Minuten
+htmlManagerServlet.helpHtmlManager=Hilfeseite HTML Manager (englisch)
+htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html
+htmlManagerServlet.helpManager=Hilfeseite Manager (englisch)
+htmlManagerServlet.helpManagerFile=manager-howto.html
+htmlManagerServlet.deployButton=Installieren
+htmlManagerServlet.deployConfig=XML Konfigurationsdatei URL:
+htmlManagerServlet.deployPath=Kontext Pfad (optional):
+htmlManagerServlet.deployServer=Verzeichnis oder WAR Datei auf Server installieren
+htmlManagerServlet.deployTitle=Installieren
+htmlManagerServlet.deployUpload=Lokale WAR Datei zur Installation hochladen
+htmlManagerServlet.deployUploadFail=FEHLER - Hochladen zur Installation fehlgeschlagen, Ausnahme: {0}
+htmlManagerServlet.deployUploadFile=WAR Datei ausw\u00e4hlen
+htmlManagerServlet.deployUploadNotWar=FEHLER - Hochgeladene Datei \"{0}\" muss ein .war sein
+htmlManagerServlet.deployUploadNoFile=FEHLER - Hochladen fehlgeschlagen, keine Datei vorhanden
+htmlManagerServlet.deployUploadWarExists=FEHLER - WAR Datei \"{0}\" existiert bereits auf Server
+htmlManagerServlet.deployWar=WAR oder Verzeichnis URL:
+htmlManagerServlet.list=Anwendungen auflisten
+htmlManagerServlet.manager=Manager
+htmlManagerServlet.messageLabel=Nachricht:
+htmlManagerServlet.serverJVMVendor=JVM Hersteller
+htmlManagerServlet.serverJVMVersion=JVM Version
+htmlManagerServlet.serverOSArch=OS Architektur
+htmlManagerServlet.serverOSName=OS Name
+htmlManagerServlet.serverOSVersion=OS Version
+htmlManagerServlet.serverTitle=Server Informationen
+htmlManagerServlet.serverVersion=Tomcat Version
+htmlManagerServlet.title=Tomcat Webanwendungs-Manager
+managerServlet.alreadyContext=FEHLER - Anwendung existiert bereits f\u00fcr Kontext Pfad {0}
+managerServlet.alreadyDocBase=FEHLER - Verzeichnis {0} bereits in Benutzung
+managerServlet.configured=OK - Anwendung von Kontext-Datei {0} installiert
+managerServlet.deployed=OK - Anwendung mit Kontext Pfad {0} installiert
+managerServlet.exception=FEHLER - Ausnahme aufgetreten {0}
+managerServlet.invalidPath=FEHLER - Ung\u00fcltiger Kontext Pfad {0} angegeben
+managerServlet.invalidWar=FEHLER - Ung\u00fcltige URL {0} f\u00fcr Anwendung angegeben
+managerServlet.listed=OK - Auflistung der Webanwendungen f\u00fcr virtuellen Server {0}
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=FEHLER - Kann Verzeichnis f\u00fcr Kontext Pfad {0} nicht finden
+managerServlet.noCommand=FEHLER - Es wurde kein Kommando angegeben
+managerServlet.noContext=FEHLER - Es existiert kein Kontext f\u00fcr Pfad {0}
+managerServlet.noDirectory=FEHLER - Pfad {0} ist kein Verzeichnis
+managerServlet.noDocBase=FEHLER - Kann Webanwendungs-Verzeichnis nicht entfernen f\u00fcr Kontext Pfad {0}
+managerServlet.noGlobal=FEHLER - Keine globalen JNDI Ressourcen verf\u00fcgbar
+managerServlet.noReload=FEHLER - Neu laden nicht unterst\u00fctzt f\u00fcr WAR mit Pfad {0}
+managerServlet.noRename=FEHLER - Kann hochgeladenes WAR mit Pfad {0} nicht installieren
+managerServlet.noRole=FEHLER - Benutzer nicht in Rolle {0}
+managerServlet.noSelf=FEHLER - Manager-Kommandos k\u00f6nnen nicht auf die Manager-Anwendung selbst angewendet werden
+managerServlet.noWrapper=Container hat setWrapper() f\u00fcr dieses Servlet nicht aufgerufen
+managerServlet.reloaded=OK - Anwendung mit Kontext Pfad {0} neu geladen
+managerServlet.resourcesAll=OK - Auflistung globaler Ressourcen (alle Typen)
+managerServlet.resourcesType=OK - Auflistung globaler Ressourcen von Typ {0}
+managerServlet.saveFail=FEHLER - Speichern der Konfiguration fehlgeschlagen: {0}
+managerServlet.sessiondefaultmax=Voreingestellter Sitzungsablauf nach maximal {0} Minuten Inaktivit\u00e4t
+#TODO: Please review the following three messages. These are displayed when "Expire sessions" button is pressed in the Manager webapp:
+managerServlet.sessiontimeout={0} Minuten: {1} Sitzungen
+managerServlet.sessiontimeout.unlimited=unlimited Minuten: {0} Sitzungen
+managerServlet.sessiontimeout.expired={0} Minuten: expired {1} Sitzungen
+managerServlet.sessions=OK - Sitzungs-Informationen f\u00fcr Anwendung mit Kontext Pfad {0}
+managerServlet.started=OK - Anwendung mit Kontext Pfad {0} gestartet
+managerServlet.startFailed=FEHLER - Anwendung mit Kontext Pfad {0} konnte nicht gestartet werden
+managerServlet.stopped=OK - Anwendung mit Kontext Pfad {0} gestoppt
+managerServlet.undeployed=OK - Anwendung mit Kontext Pfad {0} entfernt
+managerServlet.unknownCommand=FEHLER - Unbekanntes Kommando {0}
+managerServlet.userDatabaseError=FEHLER - Kann Referenz auf Benutzerdatendank nicht aufl\u00f6sen
+managerServlet.userDatabaseMissing=FEHLER - Keine Benutzerdatenbank vorhanden
+
+statusServlet.title=Server Status
+statusServlet.complete=Ausf\u00fchrlicher Server Status
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_es.properties
new file mode 100644
index 0000000..cb783f8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_es.properties
@@ -0,0 +1,101 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+htmlManagerServlet.appsAvailable = Ejecut\u00E1ndose
+htmlManagerServlet.appsName = Nombre a Mostrar
+htmlManagerServlet.appsPath = Trayectoria
+htmlManagerServlet.appsReload = Recargar
+htmlManagerServlet.appsUndeploy = Replegar
+htmlManagerServlet.appsExpire = Expirar sesiones
+htmlManagerServlet.appsSessions = Sesiones
+htmlManagerServlet.appsStart = Arrancar
+htmlManagerServlet.appsStop = Parar
+htmlManagerServlet.appsTasks = Comandos
+htmlManagerServlet.appsTitle = Aplicaciones
+htmlManagerServlet.expire.explain = sin trabajar &ge;
+htmlManagerServlet.expire.unit = minutos
+htmlManagerServlet.helpHtmlManager = Ayuda HTML de Gestor
+htmlManagerServlet.helpHtmlManagerFile = html-manager-howto.html
+htmlManagerServlet.helpManager = Ayuda de Gestor
+htmlManagerServlet.helpManagerFile = manager-howto.html
+htmlManagerServlet.deployButton = Desplegar
+htmlManagerServlet.deployConfig = URL de archivo de Configuraci\u00F3n XML\:
+htmlManagerServlet.deployPath = Trayectoria de Contexto (opcional)\:
+htmlManagerServlet.deployServer = Desplegar directorio o archivo WAR localizado en servidor
+htmlManagerServlet.deployTitle = Desplegar
+htmlManagerServlet.deployUpload = Archivo WAR a desplegar
+htmlManagerServlet.deployUploadFail = FALLO - Fall\u00F3 Carga de Despliegue, Excepci\u00F3n\: {0}
+htmlManagerServlet.deployUploadFile = Seleccione archivo WAR a cargar
+htmlManagerServlet.deployUploadInServerXml = FALLO - El fichero war "{0}" no se puede cargar si se define el contexto en server.xml 
+htmlManagerServlet.deployUploadNotWar = FALLO - El fichero cargado "{0}" debe de ser un .war
+htmlManagerServlet.deployUploadNoFile = FALLO - Fall\u00F3 la carga del fichero, no hay fichero
+htmlManagerServlet.deployUploadWarExists = FALLO - El fichero war "{0}" ya existe en el servidor
+htmlManagerServlet.deployWar = URL de WAR o Directorio\:
+htmlManagerServlet.list = Listar Aplicaciones
+htmlManagerServlet.manager = Gestor
+htmlManagerServlet.messageLabel = Mensaje\:
+htmlManagerServlet.noManager = -
+htmlManagerServlet.serverJVMVendor = Vendedor JVM
+htmlManagerServlet.serverJVMVersion = Versi\u00F3n JVM
+htmlManagerServlet.serverOSArch = Arquitectura de SO
+htmlManagerServlet.serverOSName = Nombre de SO
+htmlManagerServlet.serverOSVersion = Versi\u00F3n de SO
+htmlManagerServlet.serverTitle = Informaci\u00F3n de Servidor
+htmlManagerServlet.serverVersion = Versi\u00F3n de Tomcat
+htmlManagerServlet.title = Gestor de Aplicaciones Web de Tomcat
+managerServlet.alreadyContext = FALLO - Ya existe la aplicaci\u00F3n en la trayectoria {0}
+managerServlet.alreadyDocBase = FALLO - Directorio {0} ya est\u00E1 siendo usado
+managerServlet.cannotInvoke = No puedo invocar servlet de gestor a trav\u00E9s de invocador
+managerServlet.configured = OK - Desplegada aplicaci\u00F3n desde archivo de contexto {0}
+managerServlet.deployed = OK - Desplegada aplicaci\u00F3n en trayectoria de contexto {0}
+managerServlet.deployFailed = FALLO - No pude desplegar la aplicaci\u00F3n en ruta de contexto {0}
+managerServlet.exception = FALLO - Encontrada excepci\u00F3n {0}
+managerServlet.invalidPath = FALLO - Se ha especificado una trayectoria inv\u00E1lida de contexto {0}
+managerServlet.invalidWar = FALLO - Se ha especificado una URL de aplicaci\u00F3n inv\u00E1lida {0}
+managerServlet.listed = OK - Aplicaciones listadas para m\u00E1quinda virutal {0}
+managerServlet.listitem = {0}\:{1}\:{2}\:{3}
+managerServlet.noAppBase = FALLO - No puedo identificar aplicaci\u00F3n base para trayectoria de contexto {0}
+managerServlet.noCommand = FALLO - No se ha especificado comando
+managerServlet.noContext = FALLO - No existe contexto para trayectoria {0}
+managerServlet.noDirectory = FALLO - Documento base No-directorio para trayectoria {0}
+managerServlet.noDocBase = FALLO - No puedo replegar documento base para trayectoria {0}
+managerServlet.noGlobal = FALLO - No hay disponibles recursos globales JNDI 
+managerServlet.noManager = FALLO - No existe gestor para ruta {0}
+managerServlet.noReload = FALLO - Recarga no soportada en WAR desplegado en trayectoria {0}
+managerServlet.noRename = FALLO - No pudeo desplegar WAR cargado para trayectoria {0}
+managerServlet.noRole = FALLO - El usuario no desempe\u00F1a el papel de {0}
+managerServlet.noSelf = FALLO - El gestor no puede recargarse, replegarse, pararse o replegarse a s\u00ED mismo
+managerServlet.noWrapper = El Contenedor no ha llamado a setWrapper() para este servlet
+managerServlet.notDeployed = FALLO - El contexto {0} est\u00E1 definido en server.xml y puede que no est\u00E9 desplegado
+managerServlet.reloaded = OK - Recargada aplicaci\u00F3n en trayectoria de contexto {0}
+managerServlet.resourcesAll = OK - Listados recursos globales de todos los tipos
+managerServlet.resourcesType = OK - Listados recursos globales de tipo {0}
+managerServlet.saveFail = FAIL - Fallo al guardar la configuraci\u00F3n\: {0}
+managerServlet.saved = OK - Configuraci\u00F3n de Servidor guardada
+managerServlet.savedContext = OK - Configuraci\u00F3n de Contexto {0} guardada
+managerServlet.sessiondefaultmax = Intervalo m\u00E1ximo por defecto de sesi\u00F3n inactiva {0} minutos
+#TODO: Please review the following three messages. These are displayed when "Expire sessions" button is pressed in the Manager webapp:
+managerServlet.sessiontimeout={0} minutos: {1} sesiones
+managerServlet.sessiontimeout.unlimited=unlimited minutos: {0} sesiones
+managerServlet.sessiontimeout.expired={0} minutos: expired {1} sesiones
+managerServlet.sessions = OK - Informaci\u00F3n de sesi\u00F3n para aplicaci\u00F3n en trayectoria de contexto {0}
+managerServlet.started = OK - Arrancada aplicaci\u00F3n en trayectoria de contexto {0}
+managerServlet.startFailed = FALLO - No se pudo arrancar la aplicaci\u00F3n en trayectoria de contexto {0}
+managerServlet.stopped = OK - Parada aplicaci\u00F3n en trayectoria de contexto {0}
+managerServlet.undeployed = OK - Replegada aplicaci\u00F3n en trayectoria de contexto {0}
+managerServlet.unknownCommand = FALLO - Comando desconocido {0}
+managerServlet.userDatabaseError = FALLO - No puedo resolver referencia de base de datos de usuario
+managerServlet.userDatabaseMissing = FALLO - No se encuentra disponible base de datos de usuario
+statusServlet.title = Estado de Servidor
+statusServlet.complete = Estado Completo de Servidor
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_fr.properties
new file mode 100644
index 0000000..4a1c9d5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_fr.properties
@@ -0,0 +1,106 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+htmlManagerServlet.appsAvailable=Fonctionnelle
+htmlManagerServlet.appsName=Nom d''affichage
+htmlManagerServlet.appsPath=Chemin
+htmlManagerServlet.appsReload=Recharger
+htmlManagerServlet.appsUndeploy=Retirer
+htmlManagerServlet.appsExpire=Expirer les sessions
+htmlManagerServlet.appsSessions=Sessions
+htmlManagerServlet.appsStart=D\u00e9marrer
+htmlManagerServlet.appsStop=Arr\u00eater
+htmlManagerServlet.appsTasks=Commandes
+htmlManagerServlet.appsTitle=Applications
+htmlManagerServlet.expire.explain=inactives depuis &ge;
+htmlManagerServlet.expire.unit=minutes
+htmlManagerServlet.helpHtmlManager=Aide HTML Gestionnaire
+htmlManagerServlet.helpHtmlManagerFile=../docs/html-manager-howto.html
+htmlManagerServlet.helpManager=Aide Gestionnaire
+htmlManagerServlet.helpManagerFile=../docs/manager-howto.html
+htmlManagerServlet.deployButton=Deployer
+htmlManagerServlet.deployConfig=URL du fichier XML de configuration:
+htmlManagerServlet.deployPath=Chemin de context (requis):
+htmlManagerServlet.deployServer=Emplacement du r\u00e9pertoire ou fichier WAR de d\u00e9ploiement sur le serveur
+htmlManagerServlet.deployTitle=Deployer
+htmlManagerServlet.deployUpload=Fichier WAR \u00e0 d\u00e9ployer
+htmlManagerServlet.deployUploadFail=ECHEC - T\u00e9l\u00e9versement pour d\u00e9ploiement a \u00e9chou\u00e9, exception: {0}
+htmlManagerServlet.deployUploadFile=Choisir le fichier WAR \u00e0 t\u00e9l\u00e9verser
+htmlManagerServlet.deployUploadInServerXml=ECHEC - Fichier WAR \"{0}\" ne peut \u00eatre t\u00e9l\u00e9vers\u00e9 lorsque le contexte est d\u00e9fini dans server.xml
+htmlManagerServlet.deployUploadNotWar=ECHEC - Fichier \u00e0 t\u00e9l\u00e9verser, \"{0}\", doit \u00eatre un .war
+htmlManagerServlet.deployUploadNoFile=ECHEC - T\u00e9l\u00e9versement a \u00e9chou\u00e9, aucun fichier
+htmlManagerServlet.deployUploadWarExists=ECHEC - Fichier War \"{0}\" d\u00e9j\u00ea existant sur le serveur
+htmlManagerServlet.deployWar=URL vers WAR ou r\u00e9pertoire:
+htmlManagerServlet.list=Lister les applications
+htmlManagerServlet.manager=Gestionnaire
+htmlManagerServlet.messageLabel=Message:
+htmlManagerServlet.noManager=-
+htmlManagerServlet.serverJVMVendor=Fournisseur de la JVM
+htmlManagerServlet.serverJVMVersion=Version de la JVM
+htmlManagerServlet.serverOSArch=Architecture d''OS
+htmlManagerServlet.serverOSName=Nom d''OS
+htmlManagerServlet.serverOSVersion=Version d''OS
+htmlManagerServlet.serverTitle=Serveur
+htmlManagerServlet.serverVersion=Version de serveur
+htmlManagerServlet.title=Gestionnaire d''applications WEB Tomcat
+managerServlet.alreadyContext=ECHEC - l''application existe d\u00e9j\u00e0 dans le chemin {0}
+managerServlet.alreadyDocBase=ECHEC - Le r\u00e9pertoire {0} est d\u00e9j\u00e0 utilis\u00e9
+managerServlet.configured=OK - Application configur\u00e9e depuis le fichier contexte {0}
+managerServlet.deployed=OK - Application d\u00e9ploy\u00e9e pour le chemin de contexte {0}
+managerServlet.deployFailed=ECHEC - Echec au d\u00e9ploiement de l''application pour le chemin de contexte {0}
+managerServlet.deployedButNotStarted=ECHEC - Application d\u00e9ploy\u00e9e pour le chemin de contexte {0} mais le d\u00e9marrage du contexte a \u00e9chou\u00e9
+managerServlet.exception=ECHEC - L''exception {0} a \u00e9t\u00e9 rencontr\u00e9e
+managerServlet.invalidPath=ECHEC - Un chemin de contexte invalide {0} a \u00e9t\u00e9 sp\u00e9cifi\u00e9
+managerServlet.invalidWar=ECHEC - Une URL d''application invalide {0} a \u00e9t\u00e9 sp\u00e9cifi\u00e9e
+managerServlet.listed=OK - Applications list\u00e9es pour l''h\u00f4te virtuel (virtual host) {0}
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=ECHEC - Impossible d''identifier la base de l''application base pour le chemin de contexte {0}
+managerServlet.noCommand=ECHEC - Aucune commande n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9e
+managerServlet.noContext=ECHEC - Aucun contexte n''existe pour le chemin {0}
+managerServlet.noDirectory=ECHEC - La base de document n''est pas un r\u00e9pertoire pour le chemin {0}
+managerServlet.noDocBase=ECHEC - Impossible de retirer la base de document pour le chemin {0}
+managerServlet.noGlobal=ECHEC - Aucune ressource JNDI globale n''est disponible
+managerServlet.noManager=ECHEC - Aucun gestionnaire n''existe pour le chemin {0}
+managerServlet.noReload=ECHEC - Rechargement non support\u00e9 par le WAR d\u00e9ploy\u00e9 au chemin {0}
+managerServlet.noRename=ECHEC - Impossible de d\u00e9ployer un WAR t\u00e9l\u00e9charg\u00e9 pour le chemin {0}
+managerServlet.noRole=ECHEC - L''utilisateur ne poss\u00e8de pas le r\u00f4le {0}
+managerServlet.noSelf=ECHEC - Le gestionnaire ne peut se recharger, se retirer, s''arr\u00eater, ou se d\u00e9ployer lui-m\u00eame
+managerServlet.noWrapper=Le conteneur n''a pas appel\u00e9 "setWrapper()" pour cette servlet
+managerServlet.notDeployed=ECHEC - Le contexte {0} est d\u00e9fini dans server.xml et ne peut \u00eatre retir\u00e9
+managerServlet.reloaded=OK - L''application associ\u00e9e au chemin de contexte {0} a \u00e9t\u00e9 recharg\u00e9e
+managerServlet.resourcesAll=OK - Liste des ressources globales de tout type
+managerServlet.resourcesType=OK - Liste des ressources globales de type {0}
+managerServlet.saveFail=ECHEC - La sauvegarde de la configuration a \u00e9chou\u00e9: {0}
+managerServlet.saved=OK - Configuration serveur sauvegard\u00e9e
+managerServlet.savedContext=OK - Configuration du contexte {0} sauvegard\u00e9e
+managerServlet.sessiondefaultmax=Interval par d\u00e9faut de maximum de session inactive {0} minutes
+#TODO: Please review the following three messages. These are displayed when "Expire sessions" button is pressed in the Manager webapp:
+managerServlet.sessiontimeout={0} minutes: {1} sessions
+managerServlet.sessiontimeout.unlimited=unlimited time: {0} sessions
+managerServlet.sessiontimeout.expired={0} minutes: {1} sessions were expired
+managerServlet.sessions=OK - Information de session pour l''application au chemin de contexte {0}
+managerServlet.started=OK - Application d\u00e9marr\u00e9e pour le chemin de contexte {0}
+managerServlet.startFailed=ECHEC - L''application pour le chemin de contexte {0} n''a pas pu \u00eatre d\u00e9marr\u00e9e
+managerServlet.stopped=OK - Application arr\u00eat\u00e9e pour le chemin de contexte {0}
+managerServlet.undeployed=OK - L''application associ\u00e9e au chemin de contexte {0} a \u00e9t\u00e9 retir\u00e9e
+managerServlet.startFailed=ECHEC - L''application pour le chemin de contexte {0} n''a pas pu \u00eatre d\u00e9marr\u00e9e
+managerServlet.stopped=OK - Application arr\u00eat\u00e9e pour le chemin de contexte {0}
+managerServlet.undeployed=OK - Application non d\u00e9ploy\u00e9e pour le chemin de contexte {0}
+managerServlet.unknownCommand=ECHEC - Commande inconnue {0}
+managerServlet.userDatabaseError=ECHEC - Impossible de r\u00e9soudre la base de donn\u00e9es utilisateurs de r\u00e9f\u00e9rence
+managerServlet.userDatabaseMissing=ECHEC - Aucune base de donn\u00e9es utilisateurs n''est disponible
+
+statusServlet.title=Etat du serveur
+statusServlet.complete=Etat complet du serveur
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_ja.properties
new file mode 100644
index 0000000..11dca74
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/LocalStrings_ja.properties
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+htmlManagerServlet.appsAvailable=\u5b9f\u884c\u4e2d
+htmlManagerServlet.appsName=\u8868\u793a\u540d
+htmlManagerServlet.appsPath=\u30d1\u30b9
+htmlManagerServlet.appsReload=\u518d\u30ed\u30fc\u30c9
+htmlManagerServlet.appsUndeploy=\u914d\u5099\u89e3\u9664
+htmlManagerServlet.appsSessions=\u30bb\u30c3\u30b7\u30e7\u30f3
+htmlManagerServlet.appsStart=\u8d77\u52d5
+htmlManagerServlet.appsStop=\u505c\u6b62
+htmlManagerServlet.appsTasks=\u30b3\u30de\u30f3\u30c9
+htmlManagerServlet.appsTitle=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3
+htmlManagerServlet.helpHtmlManager=HTML\u30de\u30cd\u30fc\u30b8\u30e3\u30d8\u30eb\u30d7
+htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html
+htmlManagerServlet.helpManager=\u30de\u30cd\u30fc\u30b8\u30e3\u30d8\u30eb\u30d7
+htmlManagerServlet.helpManagerFile=manager-howto.html
+htmlManagerServlet.deployButton=\u914d\u5099
+htmlManagerServlet.deployConfig=XML\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eURL:
+htmlManagerServlet.deployPath=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 (\u7701\u7565\u53ef):
+htmlManagerServlet.deployServer=\u30b5\u30fc\u30d0\u4e0a\u306eWAR\u30d5\u30a1\u30a4\u30eb\u53c8\u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u914d\u5099
+htmlManagerServlet.deployTitle=\u914d\u5099
+htmlManagerServlet.deployUpload=WAR\u30d5\u30a1\u30a4\u30eb\u306e\u914d\u5099
+htmlManagerServlet.deployUploadFail=FAIL - \u914d\u5099\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f\u3001\u4f8b\u5916: {0}
+htmlManagerServlet.deployUploadFile=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308bWAR\u30d5\u30a1\u30a4\u30eb\u306e\u9078\u629e
+htmlManagerServlet.deployUploadNotWar=FAIL - \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308b\u30d5\u30a1\u30a4\u30eb \"{0}\" \u306fWAR\u30d5\u30a1\u30a4\u30eb\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+htmlManagerServlet.deployUploadNoFile=FAIL - \u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f\u3001\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+htmlManagerServlet.deployUploadWarExists=FAIL - WAR\u30d5\u30a1\u30a4\u30eb \"{0}\" \u306f\u65e2\u306b\u30b5\u30fc\u30d0\u4e0a\u306b\u5b58\u5728\u3057\u307e\u3059
+htmlManagerServlet.deployWar=WAR\u30d5\u30a1\u30a4\u30eb\u53c8\u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306eURL:
+htmlManagerServlet.list=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u4e00\u89a7
+htmlManagerServlet.manager=\u30de\u30cd\u30fc\u30b8\u30e3
+htmlManagerServlet.messageLabel=\u30e1\u30c3\u30bb\u30fc\u30b8
+htmlManagerServlet.serverJVMVendor=JVM\u30d9\u30f3\u30c0
+htmlManagerServlet.serverJVMVersion=JVM\u30d0\u30fc\u30b8\u30e7\u30f3
+htmlManagerServlet.serverOSArch=OS\u30a2\u30fc\u30ad\u30c6\u30af\u30c1\u30e3
+htmlManagerServlet.serverOSName=OS\u540d
+htmlManagerServlet.serverOSVersion=OS\u30d0\u30fc\u30b8\u30e7\u30f3
+htmlManagerServlet.serverTitle=\u30b5\u30fc\u30d0\u60c5\u5831
+htmlManagerServlet.serverVersion=Tomcat\u30d0\u30fc\u30b8\u30e7\u30f3
+htmlManagerServlet.title=Tomcat Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30de\u30cd\u30fc\u30b8\u30e3
+managerServlet.alreadyContext=FAIL - \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3001\u65e2\u306b\u30d1\u30b9 {0} \u306b\u5b58\u5728\u3057\u307e\u3059
+managerServlet.alreadyDocBase=FAIL - \u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306f\u65e2\u306b\u4f7f\u7528\u3055\u308c\u3066\u3044\u307e\u3059
+managerServlet.cannotInvoke=\u30a4\u30f3\u30dc\u30fc\u30ab\u3067\u30de\u30cd\u30fc\u30b8\u30e3\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u3092\u8d77\u52d5\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+managerServlet.configured=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb {0} \u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3057\u305f
+managerServlet.deployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u307e\u3057\u305f
+managerServlet.exception=FAIL - \u4f8b\u5916 {0} \u304c\u767a\u751f\u3057\u307e\u3057\u305f
+managerServlet.invalidPath=FAIL - \u7121\u52b9\u306a\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f
+managerServlet.invalidWar=FAIL - \u7121\u52b9\u306a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306eURL {0} \u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f
+managerServlet.listed=OK - \u30d0\u30fc\u30c1\u30e3\u30eb\u30db\u30b9\u30c8 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u4e00\u89a7\u3067\u3059
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=FAIL - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306b\u5bfe\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30d9\u30fc\u30b9\u3092\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093
+managerServlet.noCommand=FAIL - \u30b3\u30de\u30f3\u30c9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+managerServlet.noContext=FAIL - \u30d1\u30b9 {0} \u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+managerServlet.noDirectory=FAIL - \u30d1\u30b9 {0} \u306b\u5bfe\u3059\u308b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093
+managerServlet.noDocBase=FAIL - \u30d1\u30b9 {0} \u306b\u5bfe\u3059\u308b\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093
+managerServlet.noGlobal=FAIL - \u30b0\u30ed\u30fc\u30d0\u30eb\u306aJNDI\u30ea\u30bd\u30fc\u30b9\u304c\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+managerServlet.noReload=FAIL - \u30d1\u30b9 {0} \u306b\u914d\u5099\u3055\u308c\u305fWAR\u30d5\u30a1\u30a4\u30eb\u3067\u306f\u518d\u30ed\u30fc\u30c9\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+managerServlet.noRename=FAIL - \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3055\u308c\u305fWAR\u30d5\u30a1\u30a4\u30eb\u3092\u30d1\u30b9 {0} \u306b\u914d\u5099\u3067\u304d\u307e\u305b\u3093
+managerServlet.noRole=FAIL - \u30e6\u30fc\u30b6\u306f\u30ed\u30fc\u30eb {0} \u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093
+managerServlet.noSelf=FAIL - \u30de\u30cd\u30fc\u30b8\u30e3\u81ea\u8eab\u3092\u518d\u30ed\u30fc\u30c9\u3001\u524a\u9664\u3001\u505c\u6b62\u3001\u53c8\u306f\u914d\u5099\u89e3\u9664\u3067\u304d\u307e\u305b\u3093
+managerServlet.noWrapper=\u30b3\u30f3\u30c6\u30ca\u306f\u3053\u306e\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u547c\u3073\u51fa\u3055\u308c\u305fsetWrapper()\u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093
+managerServlet.reloaded=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f
+managerServlet.resourcesAll=OK - \u3059\u3079\u3066\u306e\u30bf\u30a4\u30d7\u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30ea\u30bd\u30fc\u30b9\u3092\u5217\u6319\u3057\u307e\u3057\u305f
+managerServlet.resourcesType=OK - \u30bf\u30a4\u30d7 {0} \u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30ea\u30bd\u30fc\u30b9\u3092\u5217\u6319\u3057\u307e\u3057\u305f
+managerServlet.saveFail=FAIL - \u8a2d\u5b9a\u306e\u4fdd\u5b58\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+managerServlet.saved=OK - \u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f
+managerServlet.savedContext=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {0} \u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f
+managerServlet.sessiondefaultmax=\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u6700\u5927\u30bb\u30c3\u30b7\u30e7\u30f3\u505c\u6b62\u9593\u9694\u306f{0}\u5206\u3067\u3059
+#TODO: Please review the following three messages. These are displayed when "Expire sessions" button is pressed in the Manager webapp:
+managerServlet.sessiontimeout={0}\u5206: {1}\u30bb\u30c3\u30b7\u30e7\u30f3
+managerServlet.sessiontimeout.unlimited=unlimited \u5206: {0}\u30bb\u30c3\u30b7\u30e7\u30f3
+managerServlet.sessiontimeout.expired={0}\u5206: expired {1}\u30bb\u30c3\u30b7\u30e7\u30f3
+managerServlet.sessions=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30bb\u30c3\u30b7\u30e7\u30f3\u60c5\u5831\u3067\u3059
+managerServlet.started=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u307e\u3057\u305f
+managerServlet.startFailed=FAIL - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u8d77\u52d5\u3067\u304d\u307e\u305b\u3093
+managerServlet.stopped=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u505c\u6b62\u3057\u307e\u3057\u305f
+managerServlet.undeployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u89e3\u9664\u3057\u307e\u3057\u305f
+managerServlet.unknownCommand=FAIL - \u672a\u77e5\u306e\u30b3\u30de\u30f3\u30c9 {0} \u3067\u3059
+managerServlet.userDatabaseError=FAIL - \u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u53c2\u7167\u3092\u89e3\u6c7a\u3067\u304d\u307e\u305b\u3093
+managerServlet.userDatabaseMissing=FAIL - \u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+statusServlet.title=\u30b5\u30fc\u30d0\u306e\u72b6\u614b
+statusServlet.complete=\u30b5\u30fc\u30d0\u306e\u5168\u72b6\u614b
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/ManagerServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/ManagerServlet.java
new file mode 100644
index 0000000..3440f3b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/ManagerServlet.java
@@ -0,0 +1,1673 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.manager;
+
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Locale;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.naming.Binding;
+import javax.naming.NamingEnumeration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerServlet;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Server;
+import org.apache.catalina.Session;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.core.StandardServer;
+import org.apache.catalina.util.ContextName;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Servlet that enables remote management of the web applications installed
+ * within the same virtual host as this web application is.  Normally, this
+ * functionality will be protected by a security constraint in the web
+ * application deployment descriptor.  However, this requirement can be
+ * relaxed during testing.
+ * <p>
+ * This servlet examines the value returned by <code>getPathInfo()</code>
+ * and related query parameters to determine what action is being requested.
+ * The following actions and parameters (starting after the servlet path)
+ * are supported:
+ * <ul>
+ * <li><b>/deploy?config={config-url}</b> - Install and start a new
+ *     web application, based on the contents of the context configuration
+ *     file found at the specified URL.  The <code>docBase</code> attribute
+ *     of the context configuration file is used to locate the actual
+ *     WAR or directory containing the application.</li>
+ * <li><b>/deploy?config={config-url}&war={war-url}/</b> - Install and start
+ *     a new web application, based on the contents of the context
+ *     configuration file found at <code>{config-url}</code>, overriding the
+ *     <code>docBase</code> attribute with the contents of the web
+ *     application archive found at <code>{war-url}</code>.</li>
+ * <li><b>/deploy?path=/xxx&war={war-url}</b> - Install and start a new
+ *     web application attached to context path <code>/xxx</code>, based
+ *     on the contents of the web application archive found at the
+ *     specified URL.</li>
+ * <li><b>/list</b> - List the context paths of all currently installed web
+ *     applications for this virtual host.  Each context will be listed with
+ *     the following format <code>path:status:sessions</code>.
+ *     Where path is the context path.  Status is either running or stopped.
+ *     Sessions is the number of active Sessions.</li>
+ * <li><b>/reload?path=/xxx</b> - Reload the Java classes and resources for
+ *     the application at the specified path.</li>
+ * <li><b>/resources?type=xxxx</b> - Enumerate the available global JNDI
+ *     resources, optionally limited to those of the specified type
+ *     (fully qualified Java class name), if available.</li>
+ * <li><b>/serverinfo</b> - Display system OS and JVM properties.
+ * <li><b>/sessions</b> - Deprecated. Use expire.
+ * <li><b>/expire?path=/xxx</b> - List session idle timeinformation about the
+ *     web application attached to context path <code>/xxx</code> for this
+ *     virtual host.</li>
+ * <li><b>/expire?path=/xxx&idle=mm</b> - Expire sessions
+ *     for the context path <code>/xxx</code> which were idle for at
+ *     least mm minutes.</li>
+ * <li><b>/start?path=/xxx</b> - Start the web application attached to
+ *     context path <code>/xxx</code> for this virtual host.</li>
+ * <li><b>/stop?path=/xxx</b> - Stop the web application attached to
+ *     context path <code>/xxx</code> for this virtual host.</li>
+ * <li><b>/undeploy?path=/xxx</b> - Shutdown and remove the web application
+ *     attached to context path <code>/xxx</code> for this virtual host,
+ *     and remove the underlying WAR file or document base directory.
+ *     (<em>NOTE</em> - This is only allowed if the WAR file or document
+ *     base is stored in the <code>appBase</code> directory of this host,
+ *     typically as a result of being placed there via the <code>/deploy</code>
+ *     command.</li>
+ * </ul>
+ * <p>Use <code>path=/</code> for the ROOT context.</p>
+ * <p>The syntax of the URL for a web application archive must conform to one
+ * of the following patterns to be successfully deployed:</p>
+ * <ul>
+ * <li><b>file:/absolute/path/to/a/directory</b> - You can specify the absolute
+ *     path of a directory that contains the unpacked version of a web
+ *     application.  This directory will be attached to the context path you
+ *     specify without any changes.</li>
+ * <li><b>jar:file:/absolute/path/to/a/warfile.war!/</b> - You can specify a
+ *     URL to a local web application archive file.  The syntax must conform to
+ *     the rules specified by the <code>JarURLConnection</code> class for a
+ *     reference to an entire JAR file.</li>
+ * <li><b>jar:http://hostname:port/path/to/a/warfile.war!/</b> - You can specify
+ *     a URL to a remote (HTTP-accessible) web application archive file.  The
+ *     syntax must conform to the rules specified by the
+ *     <code>JarURLConnection</code> class for a reference to an entire
+ *     JAR file.</li>
+ * </ul>
+ * <p>
+ * <b>NOTE</b> - Attempting to reload or remove the application containing
+ * this servlet itself will not succeed.  Therefore, this servlet should
+ * generally be deployed as a separate web application within the virtual host
+ * to be managed.
+ * <p>
+ * The following servlet initialization parameters are recognized:
+ * <ul>
+ * <li><b>debug</b> - The debugging detail level that controls the amount
+ *     of information that is logged by this servlet.  Default is zero.
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: ManagerServlet.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+
+public class ManagerServlet extends HttpServlet implements ContainerServlet {
+
+    private static final long serialVersionUID = 1L;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Path where context descriptors should be deployed.
+     */
+    protected File configBase = null;
+
+
+    /**
+     * The Context container associated with our web application.
+     */
+    protected transient Context context = null;
+
+
+    /**
+     * The debugging detail level for this servlet.
+     */
+    protected int debug = 1;
+
+
+    /**
+     * File object representing the directory into which the deploy() command
+     * will store the WAR and context configuration files that have been
+     * uploaded.
+     */
+    protected File deployed = null;
+
+
+    /**
+     * Path used to store revisions of webapps.
+     */
+    protected File versioned = null;
+
+
+    /**
+     * Path used to store context descriptors.
+     */
+    protected File contextDescriptors = null;
+
+
+    /**
+     * The associated host.
+     */
+    protected transient Host host = null;
+
+    
+    /**
+     * The host appBase.
+     */
+    protected File appBase = null;
+    
+    
+    /**
+     * MBean server.
+     */
+    protected transient MBeanServer mBeanServer = null;
+
+
+    /**
+     * The associated deployer ObjectName.
+     */
+    protected ObjectName oname = null;
+    
+
+    /**
+     * The global JNDI <code>NamingContext</code> for this server,
+     * if available.
+     */
+    protected transient javax.naming.Context global = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The Wrapper container associated with this servlet.
+     */
+    protected transient Wrapper wrapper = null;
+
+
+    // ----------------------------------------------- ContainerServlet Methods
+
+
+    /**
+     * Return the Wrapper with which we are associated.
+     */
+    @Override
+    public Wrapper getWrapper() {
+
+        return (this.wrapper);
+
+    }
+
+
+    /**
+     * Set the Wrapper with which we are associated.
+     *
+     * @param wrapper The new wrapper
+     */
+    @Override
+    public void setWrapper(Wrapper wrapper) {
+
+        this.wrapper = wrapper;
+        if (wrapper == null) {
+            context = null;
+            host = null;
+            oname = null;
+        } else {
+            context = (Context) wrapper.getParent();
+            host = (Host) context.getParent();
+            Engine engine = (Engine) host.getParent();
+            String name = engine.getName() + ":type=Deployer,host=" +
+                    host.getName();
+            try {
+                oname = new ObjectName(name);
+            } catch (Exception e) {
+                log(sm.getString("managerServlet.objectNameFail", name), e);
+            }
+        }
+
+        // Retrieve the MBean server
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+        
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize this servlet.
+     */
+    @Override
+    public void destroy() {
+
+        // No actions necessary
+
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        StringManager smClient = getStringManager(request);
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+        if (command == null)
+            command = request.getServletPath();
+        String config = request.getParameter("config");
+        String path = request.getParameter("path");
+        ContextName cn = null;
+        if (path != null) {
+            cn = new ContextName(path, request.getParameter("version"));
+        }
+        String type = request.getParameter("type");
+        String war = request.getParameter("war");
+        String tag = request.getParameter("tag");
+        boolean update = false;
+        if ((request.getParameter("update") != null) 
+            && (request.getParameter("update").equals("true"))) {
+            update = true;
+        }
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/plain; charset=" + Constants.CHARSET);
+        PrintWriter writer = response.getWriter();
+
+        // Process the requested command (note - "/deploy" is not listed here)
+        if (command == null) {
+            writer.println(smClient.getString("managerServlet.noCommand"));
+        } else if (command.equals("/deploy")) {
+            if (war != null || config != null) {
+                deploy(writer, config, cn, war, update, smClient);
+            } else {
+                deploy(writer, cn, tag, smClient);
+            }
+        } else if (command.equals("/list")) {
+            list(writer, smClient);
+        } else if (command.equals("/reload")) {
+            reload(writer, cn, smClient);
+        } else if (command.equals("/resources")) {
+            resources(writer, type, smClient);
+        } else if (command.equals("/save")) {
+            save(writer, path, smClient);
+        } else if (command.equals("/serverinfo")) {
+            serverinfo(writer, smClient);
+        } else if (command.equals("/sessions")) {
+            expireSessions(writer, cn, request, smClient);
+        } else if (command.equals("/expire")) {
+            expireSessions(writer, cn, request, smClient);
+        } else if (command.equals("/start")) {
+            start(writer, cn, smClient);
+        } else if (command.equals("/stop")) {
+            stop(writer, cn, smClient);
+        } else if (command.equals("/undeploy")) {
+            undeploy(writer, cn, smClient);
+        } else if (command.equals("/findleaks")) {
+            findleaks(writer, smClient);
+        } else {
+            writer.println(smClient.getString("managerServlet.unknownCommand",
+                    command));
+        }
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+
+    }
+
+
+    /**
+     * Process a PUT request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    public void doPut(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        StringManager smClient = getStringManager(request);
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+        if (command == null)
+            command = request.getServletPath();
+        String path = request.getParameter("path");
+        ContextName cn = null;
+        if (path != null) {
+            cn = new ContextName(path, request.getParameter("version"));
+        }
+        String tag = request.getParameter("tag");
+        boolean update = false;
+        if ((request.getParameter("update") != null) 
+            && (request.getParameter("update").equals("true"))) {
+            update = true;
+        }
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/plain;charset="+Constants.CHARSET);
+        PrintWriter writer = response.getWriter();
+
+        // Process the requested command
+        if (command == null) {
+            writer.println(smClient.getString("managerServlet.noCommand"));
+        } else if (command.equals("/deploy")) {
+            deploy(writer, cn, tag, update, request, smClient);
+        } else {
+            writer.println(smClient.getString("managerServlet.unknownCommand",
+                    command));
+        }
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+
+    }
+
+
+    /**
+     * Initialize this servlet.
+     */
+    @Override
+    public void init() throws ServletException {
+
+        // Ensure that our ContainerServlet properties have been set
+        if ((wrapper == null) || (context == null))
+            throw new UnavailableException(
+                    sm.getString("managerServlet.noWrapper"));
+
+        // Set our properties from the initialization parameters
+        String value = null;
+        try {
+            value = getServletConfig().getInitParameter("debug");
+            debug = Integer.parseInt(value);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+        }
+
+        // Acquire global JNDI resources if available
+        Server server = ((Engine)host.getParent()).getService().getServer();
+        if (server != null) {
+            global = server.getGlobalNamingContext();
+        }
+
+        // Calculate the directory into which we will be deploying applications
+        versioned = (File) getServletContext().getAttribute
+            (ServletContext.TEMPDIR);
+
+        // Identify the appBase of the owning Host of this Context
+        // (if any)
+        String appBase = ((Host) context.getParent()).getAppBase();
+        deployed = new File(appBase);
+        if (!deployed.isAbsolute()) {
+            deployed = new File(System.getProperty(Globals.CATALINA_BASE_PROP),
+                                appBase);
+        }
+        configBase = new File(System.getProperty(Globals.CATALINA_BASE_PROP), "conf");
+        Container container = context;
+        Container host = null;
+        Container engine = null;
+        while (container != null) {
+            if (container instanceof Host)
+                host = container;
+            if (container instanceof Engine)
+                engine = container;
+            container = container.getParent();
+        }
+        if (engine != null) {
+            configBase = new File(configBase, engine.getName());
+        }
+        if (host != null) {
+            configBase = new File(configBase, host.getName());
+        }
+        // Note: The directory must exist for this to work.
+
+        // Log debugging messages as necessary
+        if (debug >= 1) {
+            log("init: Associated with Deployer '" +
+                oname + "'");
+            if (global != null) {
+                log("init: Global resources are available");
+            }
+        }
+
+    }
+
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Find potential memory leaks caused by web application reload.
+     */
+    protected void findleaks(PrintWriter writer, StringManager smClient) {
+        
+        if (!(host instanceof StandardHost)) {
+            writer.println(smClient.getString("managerServlet.findleaksFail"));
+            return;
+        }
+        
+        String[] results =
+            ((StandardHost) host).findReloadedContextMemoryLeaks();
+        
+        for (String result : results) {
+            if ("".equals(result)) {
+                result = "/";
+            }
+            writer.println(result);
+        }
+    }
+    
+    
+    /**
+     * Store server configuration.
+     * 
+     * @param path Optional context path to save
+     */
+    protected synchronized void save(PrintWriter writer, String path,
+            StringManager smClient) {
+
+        Server server = ((Engine)host.getParent()).getService().getServer();
+
+        if (!(server instanceof StandardServer)) {
+            writer.println(smClient.getString("managerServlet.saveFail",
+                    server));
+            return;
+        }
+
+        if ((path == null) || path.length() == 0 || !path.startsWith("/")) {
+            try {
+                ((StandardServer) server).storeConfig();
+                writer.println(smClient.getString("managerServlet.saved"));
+            } catch (Exception e) {
+                log("managerServlet.storeConfig", e);
+                writer.println(smClient.getString("managerServlet.exception",
+                        e.toString()));
+                return;
+            }
+        } else {
+            String contextPath = path;
+            if (path.equals("/")) {
+                contextPath = "";
+            }
+            Context context = (Context) host.findChild(contextPath);
+            if (context == null) {
+                writer.println(smClient.getString("managerServlet.noContext",
+                        path));
+                return;
+            }
+            try {
+                ((StandardServer) server).storeContext(context);
+                writer.println(smClient.getString("managerServlet.savedContext",
+                        path));
+            } catch (Exception e) {
+                log("managerServlet.save[" + path + "]", e);
+                writer.println(smClient.getString("managerServlet.exception",
+                        e.toString()));
+                return;
+            }
+        }
+
+    }
+
+
+    /**
+     * Deploy a web application archive (included in the current request)
+     * at the specified context path.
+     *
+     * @param writer Writer to render results to
+     * @param cn Name of the application to be installed
+     * @param tag Tag to be associated with the webapp
+     * @param request Servlet request we are processing
+     */
+    protected synchronized void deploy
+        (PrintWriter writer, ContextName cn,
+         String tag, boolean update, HttpServletRequest request,
+         StringManager smClient) {
+
+        if (debug >= 1) {
+            log("deploy: Deploying web application '" + cn + "'");
+        }
+
+        // Validate the requested context path
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+        String name = cn.getName();
+        String baseName = cn.getBaseName();
+        String displayPath = cn.getDisplayName();
+
+        // Check if app already exists, or undeploy it if updating
+        Context context = (Context) host.findChild(name);
+        if (update) {
+            if (context != null) {
+                undeploy(writer, cn, smClient);
+            }
+            context = (Context) host.findChild(name);
+        }
+        if (context != null) {
+            writer.println(smClient.getString("managerServlet.alreadyContext",
+                    displayPath));
+            return;
+        }
+
+        // Calculate the base path
+        File deployedPath = deployed;
+        if (tag != null) {
+            deployedPath = new File(versioned, tag);
+            if (!deployedPath.isDirectory() && !deployedPath.mkdirs()) {
+                writer.println(smClient.getString("managerServlet.mkdirFail",
+                        deployedPath));
+                return;
+            }
+        }
+
+        // Upload the web application archive to a local WAR file
+        File localWar = new File(deployedPath, baseName + ".war");
+        if (debug >= 2) {
+            log("Uploading WAR file to " + localWar);
+        }
+
+        // Copy WAR to appBase
+        try {
+            if (!isServiced(name)) {
+                addServiced(name);
+                try {
+                    // Upload WAR
+                    uploadWar(writer, request, localWar, smClient);
+                    // Copy WAR and XML to the host app base if needed
+                    if (tag != null) {
+                        deployedPath = deployed;
+                        File localWarCopy = new File(deployedPath, baseName + ".war");
+                        copy(localWar, localWarCopy);
+                        localWar = localWarCopy;
+                        copy(localWar, new File(getAppBase(), baseName + ".war"));
+                    }
+                    // Perform new deployment
+                    check(name);
+                } finally {
+                    removeServiced(name);
+                }
+            }
+        } catch (Exception e) {
+            log("managerServlet.check[" + displayPath + "]", e);
+            writer.println(smClient.getString("managerServlet.exception",
+                    e.toString()));
+            return;
+        }
+        
+        context = (Context) host.findChild(name);
+        if (context != null && context.getConfigured()) {
+            writer.println(smClient.getString(
+                    "managerServlet.deployed", displayPath));
+        } else {
+            // Something failed
+            writer.println(smClient.getString(
+                    "managerServlet.deployFailed", displayPath));
+        }
+        
+    }
+
+
+    /**
+     * Install an application for the specified path from the specified
+     * web application archive.
+     *
+     * @param writer Writer to render results to
+     * @param tag Revision tag to deploy from
+     * @param cn Name of the application to be installed
+     */
+    protected void deploy(PrintWriter writer, ContextName cn, String tag,
+            StringManager smClient) {
+
+        // Validate the requested context path
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+
+        String baseName = cn.getBaseName();
+        String name = cn.getName();
+        String displayPath = cn.getDisplayName();
+        
+        // Calculate the base path
+        File deployedPath = versioned;
+        if (tag != null) {
+            deployedPath = new File(deployedPath, tag);
+        }
+
+        // Find the local WAR file
+        File localWar = new File(deployedPath, baseName + ".war");
+
+        // Check if app already exists, or undeploy it if updating
+        Context context = (Context) host.findChild(name);
+        if (context != null) {
+            undeploy(writer, cn, smClient);
+        }
+
+        // Copy WAR to appBase
+        try {
+            if (!isServiced(name)) {
+                addServiced(name);
+                try {
+                    copy(localWar, new File(getAppBase(), baseName + ".war"));
+                    // Perform new deployment
+                    check(name);
+                } finally {
+                    removeServiced(name);
+                }
+            }
+        } catch (Exception e) {
+            log("managerServlet.check[" + displayPath + "]", e);
+            writer.println(smClient.getString("managerServlet.exception",
+                    e.toString()));
+            return;
+        }
+        
+        context = (Context) host.findChild(name);
+        if (context != null && context.getConfigured()) {
+            writer.println(smClient.getString("managerServlet.deployed",
+                    displayPath));
+        } else {
+            // Something failed
+            writer.println(smClient.getString("managerServlet.deployFailed",
+                    displayPath));
+        }
+        
+    }
+
+
+    /**
+     * Install an application for the specified path from the specified
+     * web application archive.
+     *
+     * @param writer Writer to render results to
+     * @param config URL of the context configuration file to be installed
+     * @param cn Name of the application to be installed
+     * @param war URL of the web application archive to be installed
+     * @param update true to override any existing webapp on the path
+     */
+    protected void deploy(PrintWriter writer, String config, ContextName cn,
+            String war, boolean update,  StringManager smClient) {
+        
+        if (config != null && config.length() == 0) {
+            config = null;
+        }
+        if (war != null && war.length() == 0) {
+            war = null;
+        }
+        
+        if (debug >= 1) {
+            if (config != null && config.length() > 0) {
+                if (war != null) {
+                    log("install: Installing context configuration at '" +
+                            config + "' from '" + war + "'");
+                } else {
+                    log("install: Installing context configuration at '" +
+                            config + "'");
+                }
+            } else {
+                if (cn != null) {
+                    log("install: Installing web application '" + cn +
+                            "' from '" + war + "'");
+                } else {
+                    log("install: Installing web application from '" + war + "'");
+                }
+            }
+        }
+        
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+        @SuppressWarnings("null") // checked in call above
+        String name = cn.getName();
+        String baseName = cn.getBaseName();
+        String displayPath = cn.getDisplayName();
+        
+        // Check if app already exists, or undeploy it if updating
+        Context context = (Context) host.findChild(name);
+        if (update) {
+            if (context != null) {
+                undeploy(writer, cn, smClient);
+            }
+            context = (Context) host.findChild(name);
+        }
+        if (context != null) {
+            writer.println(smClient.getString("managerServlet.alreadyContext",
+                    displayPath));
+            return;
+        }
+        
+        if (config != null && (config.startsWith("file:"))) {
+            config = config.substring("file:".length());
+        }
+        if (war != null && (war.startsWith("file:"))) {
+            war = war.substring("file:".length());
+        }
+        
+        try {
+            if (!isServiced(name)) {
+                addServiced(name);
+                try {
+                    if (config != null) {
+                        if (!configBase.isDirectory() && !configBase.mkdirs()) {
+                            writer.println(smClient.getString(
+                                    "managerServlet.mkdirFail",configBase));
+                            return;
+                        }
+                        copy(new File(config), 
+                                new File(configBase, baseName + ".xml"));
+                    }
+                    if (war != null) {
+                        if (war.endsWith(".war")) {
+                            copy(new File(war), 
+                                    new File(getAppBase(), baseName + ".war"));
+                        } else {
+                            copy(new File(war), 
+                                    new File(getAppBase(), baseName));
+                        }
+                    }
+                    // Perform new deployment
+                    check(name);
+                } finally {
+                    removeServiced(name);
+                }
+            }
+            context = (Context) host.findChild(name);
+            if (context != null && context.getConfigured() && context.getAvailable()) {
+                writer.println(smClient.getString(
+                        "managerServlet.deployed", displayPath));
+            } else if (context!=null && !context.getAvailable()) {
+                writer.println(smClient.getString(
+                        "managerServlet.deployedButNotStarted", displayPath));
+            } else {
+                // Something failed
+                writer.println(smClient.getString(
+                        "managerServlet.deployFailed", displayPath));
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log("ManagerServlet.install[" + displayPath + "]", t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+        
+    }
+
+
+    /**
+     * Render a list of the currently active Contexts in our virtual host.
+     *
+     * @param writer Writer to render to
+     */
+    protected void list(PrintWriter writer, StringManager smClient) {
+
+        if (debug >= 1)
+            log("list: Listing contexts for virtual host '" +
+                host.getName() + "'");
+
+        writer.println(smClient.getString("managerServlet.listed",
+                                    host.getName()));
+        Container[] contexts = host.findChildren();
+        for (int i = 0; i < contexts.length; i++) {
+            Context context = (Context) contexts[i];
+            if (context != null ) {
+                String displayPath = context.getPath();
+                if( displayPath.equals("") )
+                    displayPath = "/";
+                if (context.getAvailable()) {
+                    writer.println(smClient.getString("managerServlet.listitem",
+                            displayPath,
+                            "running",
+                            "" + context.getManager().findSessions().length,
+                            context.getDocBase()));
+                } else {
+                    writer.println(smClient.getString("managerServlet.listitem",
+                            displayPath,
+                            "stopped",
+                            "0",
+                            context.getDocBase()));
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Reload the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param cn Name of the application to be restarted
+     */
+    protected void reload(PrintWriter writer, ContextName cn,
+            StringManager smClient) {
+
+        if (debug >= 1)
+            log("restart: Reloading web application '" + cn + "'");
+
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+
+        try {
+            Context context = (Context) host.findChild(cn.getName());
+            if (context == null) {
+                writer.println(smClient.getString("managerServlet.noContext",
+                        RequestUtil.filter(cn.getDisplayName())));
+                return;
+            }
+            // It isn't possible for the manager to reload itself
+            if (context.getName().equals(this.context.getName())) {
+                writer.println(smClient.getString("managerServlet.noSelf"));
+                return;
+            }
+            context.reload();
+            writer.println(smClient.getString("managerServlet.reloaded",
+                    cn.getDisplayName()));
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log("ManagerServlet.reload[" + cn.getDisplayName() + "]", t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Render a list of available global JNDI resources.
+     *
+     * @param type Fully qualified class name of the resource type of interest,
+     *  or <code>null</code> to list resources of all types
+     */
+    protected void resources(PrintWriter writer, String type,
+            StringManager smClient) {
+
+        if (debug >= 1) {
+            if (type != null) {
+                log("resources:  Listing resources of type " + type);
+            } else {
+                log("resources:  Listing resources of all types");
+            }
+        }
+
+        // Is the global JNDI resources context available?
+        if (global == null) {
+            writer.println(smClient.getString("managerServlet.noGlobal"));
+            return;
+        }
+
+        // Enumerate the global JNDI resources of the requested type
+        if (type != null) {
+            writer.println(smClient.getString("managerServlet.resourcesType",
+                    type));
+        } else {
+            writer.println(smClient.getString("managerServlet.resourcesAll"));
+        }
+
+        Class<?> clazz = null;
+        try {
+            if (type != null) {
+                clazz = Class.forName(type);
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log("ManagerServlet.resources[" + type + "]", t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+            return;
+        }
+
+        printResources(writer, "", global, type, clazz, smClient);
+
+    }
+
+
+    /**
+     * List the resources of the given context.
+     */
+    protected void printResources(PrintWriter writer, String prefix,
+                                  javax.naming.Context namingContext,
+                                  String type, Class<?> clazz,
+                                  StringManager smClient) {
+
+        try {
+            NamingEnumeration<Binding> items = namingContext.listBindings("");
+            while (items.hasMore()) {
+                Binding item = items.next();
+                if (item.getObject() instanceof javax.naming.Context) {
+                    printResources
+                        (writer, prefix + item.getName() + "/",
+                         (javax.naming.Context) item.getObject(), type, clazz,
+                         smClient);
+                } else {
+                    if ((clazz != null) &&
+                        (!(clazz.isInstance(item.getObject())))) {
+                        continue;
+                    }
+                    writer.print(prefix + item.getName());
+                    writer.print(':');
+                    writer.print(item.getClassName());
+                    // Do we want a description if available?
+                    writer.println();
+                }
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log("ManagerServlet.resources[" + type + "]", t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Writes System OS and JVM properties.
+     * @param writer Writer to render to
+     */
+    protected void serverinfo(PrintWriter writer,  StringManager smClient) {
+        if (debug >= 1)
+            log("serverinfo");
+        try {
+            StringBuilder props = new StringBuilder();
+            props.append("OK - Server info");
+            props.append("\nTomcat Version: ");
+            props.append(ServerInfo.getServerInfo());
+            props.append("\nOS Name: ");
+            props.append(System.getProperty("os.name"));
+            props.append("\nOS Version: ");
+            props.append(System.getProperty("os.version"));
+            props.append("\nOS Architecture: ");
+            props.append(System.getProperty("os.arch"));
+            props.append("\nJVM Version: ");
+            props.append(System.getProperty("java.runtime.version"));
+            props.append("\nJVM Vendor: ");
+            props.append(System.getProperty("java.vm.vendor"));
+            writer.println(props.toString());
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            getServletContext().log("ManagerServlet.serverinfo",t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+    }
+
+    /**
+     * Session information for the web application at the specified context path.
+     * Displays a profile of session thisAccessedTime listing number
+     * of sessions for each 10 minute interval up to 10 hours.
+     *
+     * @param writer Writer to render to
+     * @param cn Name of the application to list session information for
+     * @param idle Expire all sessions with idle time &gt; idle for this context
+     */
+    protected void sessions(PrintWriter writer, ContextName cn, int idle,
+            StringManager smClient) {
+
+        if (debug >= 1) {
+            log("sessions: Session information for web application '" + cn + "'");
+            if (idle >= 0)
+                log("sessions: Session expiration for " + idle + " minutes '" + cn + "'");
+        }
+
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+
+        String displayPath = cn.getDisplayName();
+
+        try {
+            Context context = (Context) host.findChild(cn.getName());
+            if (context == null) {
+                writer.println(smClient.getString("managerServlet.noContext",
+                        RequestUtil.filter(displayPath)));
+                return;
+            }
+            Manager manager = context.getManager() ;
+            if(manager == null) {
+                writer.println(smClient.getString("managerServlet.noManager",
+                        RequestUtil.filter(displayPath)));
+                return;               
+            }
+            int maxCount = 60;
+            int maxInactiveInterval = manager.getMaxInactiveInterval()/60;
+            int histoInterval = maxInactiveInterval / maxCount;
+            if ( histoInterval * maxCount < maxInactiveInterval ) 
+                histoInterval++;
+            if (0==histoInterval)
+                histoInterval=1;
+            maxCount = maxInactiveInterval / histoInterval;
+            if ( histoInterval * maxCount < maxInactiveInterval ) 
+                maxCount++;
+
+            writer.println(smClient.getString("managerServlet.sessions",
+                    displayPath));
+            writer.println(smClient.getString(
+                    "managerServlet.sessiondefaultmax",
+                    "" + maxInactiveInterval));
+            Session [] sessions = manager.findSessions();
+            int [] timeout = new int[maxCount];
+            int notimeout = 0;
+            int expired = 0;
+            long now = System.currentTimeMillis();
+            for (int i = 0; i < sessions.length; i++) {
+                int time = (int)((now-sessions[i].getThisAccessedTimeInternal())/1000);
+                if (idle >= 0 && time >= idle*60) {
+                    sessions[i].expire();
+                    expired++;
+                }
+                time=time/60/histoInterval;
+                if (time < 0)
+                    notimeout++;
+                else if (time >= maxCount)
+                    timeout[maxCount-1]++;
+                else
+                    timeout[time]++;
+            }
+            if (timeout[0] > 0)
+                writer.println(smClient.getString(
+                        "managerServlet.sessiontimeout",
+                        "<" + histoInterval, "" + timeout[0]));
+            for (int i = 1; i < maxCount-1; i++) {
+                if (timeout[i] > 0)
+                    writer.println(smClient.getString(
+                            "managerServlet.sessiontimeout",
+                            "" + (i)*histoInterval + " - <" + (i+1)*histoInterval,
+                            "" + timeout[i]));
+            }
+            if (timeout[maxCount-1] > 0)
+                writer.println(smClient.getString(
+                        "managerServlet.sessiontimeout",
+                        ">=" + maxCount*histoInterval,
+                        "" + timeout[maxCount-1]));
+            if (notimeout > 0)
+                writer.println(smClient.getString(
+                        "managerServlet.sessiontimeout.unlimited",
+                        "" + notimeout));
+            if (idle >= 0)
+                writer.println(smClient.getString(
+                        "managerServlet.sessiontimeout.expired",
+                        "" + idle,"" + expired));
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log("ManagerServlet.sessions[" + displayPath + "]", t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Session information for the web application at the specified context path.
+     * Displays a profile of session thisAccessedTime listing number
+     * of sessions for each 10 minute interval up to 10 hours.
+     *
+     * @param writer Writer to render to
+     * @param cn Name of the application to list session information for
+     */
+    protected void sessions(PrintWriter writer, ContextName cn,
+            StringManager smClient) {
+        sessions(writer, cn, -1, smClient);
+    }
+
+
+    /**
+     *
+     * Extract the expiration request parameter
+     *
+     * @param cn
+     * @param req
+     */
+    protected void expireSessions(PrintWriter writer, ContextName cn,
+            HttpServletRequest req, StringManager smClient) {
+        int idle = -1;
+        String idleParam = req.getParameter("idle");
+        if (idleParam != null) {
+            try {
+                idle = Integer.parseInt(idleParam);
+            } catch (NumberFormatException e) {
+                log("Could not parse idle parameter to an int: " + idleParam);
+            }
+        }
+        sessions(writer, cn, idle, smClient);
+    }
+
+    /**
+     * Start the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param cn Name of the application to be started
+     */
+    protected void start(PrintWriter writer, ContextName cn,
+            StringManager smClient) {
+
+        if (debug >= 1)
+            log("start: Starting web application '" + cn + "'");
+
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+
+        String displayPath = cn.getDisplayName();
+
+        try {
+            Context context = (Context) host.findChild(cn.getName());
+            if (context == null) {
+                writer.println(smClient.getString("managerServlet.noContext", 
+                        RequestUtil.filter(displayPath)));
+                return;
+            }
+            context.start();
+            if (context.getAvailable())
+                writer.println(smClient.getString("managerServlet.started",
+                        displayPath));
+            else
+                writer.println(smClient.getString("managerServlet.startFailed",
+                        displayPath));
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            getServletContext().log(sm.getString("managerServlet.startFailed",
+                    displayPath), t);
+            writer.println(smClient.getString("managerServlet.startFailed",
+                    displayPath));
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Stop the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param cn Name of the application to be stopped
+     */
+    protected void stop(PrintWriter writer, ContextName cn,
+            StringManager smClient) {
+
+        if (debug >= 1)
+            log("stop: Stopping web application '" + cn + "'");
+
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+
+        String displayPath = cn.getDisplayName();
+
+        try {
+            Context context = (Context) host.findChild(cn.getName());
+            if (context == null) {
+                writer.println(smClient.getString("managerServlet.noContext",
+                        RequestUtil.filter(displayPath)));
+                return;
+            }
+            // It isn't possible for the manager to stop itself
+            if (context.getName().equals(this.context.getName())) {
+                writer.println(smClient.getString("managerServlet.noSelf"));
+                return;
+            }
+            context.stop();
+            writer.println(smClient.getString(
+                    "managerServlet.stopped", displayPath));
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log("ManagerServlet.stop[" + displayPath + "]", t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Undeploy the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param cn Name of the application to be removed
+     */
+    protected void undeploy(PrintWriter writer, ContextName cn,
+            StringManager smClient) {
+
+        if (debug >= 1)
+            log("undeploy: Undeploying web application at '" + cn + "'");
+
+        if (!validateContextName(cn, writer, smClient)) {
+            return;
+        }
+
+        String name = cn.getName();
+        String baseName = cn.getBaseName();
+        String displayPath = cn.getDisplayName();
+
+        try {
+
+            // Validate the Context of the specified application
+            Context context = (Context) host.findChild(name);
+            if (context == null) {
+                writer.println(smClient.getString("managerServlet.noContext",
+                        RequestUtil.filter(displayPath)));
+                return;
+            }
+
+            if (!isDeployed(name)) {
+                writer.println(smClient.getString("managerServlet.notDeployed",
+                        RequestUtil.filter(displayPath)));
+                return;
+            }
+
+            if (!isServiced(name)) {
+                addServiced(name);
+                try {
+                    // Try to stop the context first to be nicer
+                    context.stop();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+                try {
+                    File war = new File(getAppBase(), baseName + ".war");
+                    File dir = new File(getAppBase(), baseName);
+                    File xml = new File(configBase, baseName + ".xml");
+                    if (war.exists() && !war.delete()) {
+                        writer.println(smClient.getString(
+                                "managerServlet.deleteFail", war));
+                        return;
+                    } else if (dir.exists() && !undeployDir(dir)) {
+                        writer.println(smClient.getString(
+                                "managerServlet.deleteFail", dir));
+                        return;
+                    } else if (xml.exists() && !xml.delete()) {
+                        writer.println(smClient.getString(
+                                "managerServlet.deleteFail", xml));
+                        return;
+                    }
+                    // Perform new deployment
+                    check(name);
+                } finally {
+                    removeServiced(name);
+                }
+            }
+            writer.println(smClient.getString("managerServlet.undeployed",
+                    displayPath));
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log("ManagerServlet.undeploy[" + displayPath + "]", t);
+            writer.println(smClient.getString("managerServlet.exception",
+                    t.toString()));
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Support Methods
+
+
+    /**
+     * Return a File object representing the "application root" directory
+     * for our associated Host.
+     */
+    protected File getAppBase() {
+
+        if (appBase != null) {
+            return appBase;
+        }
+
+        File file = new File(host.getAppBase());
+        if (!file.isAbsolute())
+            file = new File(System.getProperty(Globals.CATALINA_BASE_PROP),
+                            host.getAppBase());
+        try {
+            appBase = file.getCanonicalFile();
+        } catch (IOException e) {
+            appBase = file;
+        }
+        return (appBase);
+
+    }
+
+
+    /**
+     * Invoke the isDeployed method on the deployer.
+     */
+    protected boolean isDeployed(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        Boolean result = 
+            (Boolean) mBeanServer.invoke(oname, "isDeployed", params, signature);
+        return result.booleanValue();
+    }
+    
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected void check(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "check", params, signature);
+    }
+    
+
+    /**
+     * Invoke the isServiced method on the deployer.
+     */
+    protected boolean isServiced(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        Boolean result = 
+            (Boolean) mBeanServer.invoke(oname, "isServiced", params, signature);
+        return result.booleanValue();
+    }
+    
+
+    /**
+     * Invoke the addServiced method on the deployer.
+     */
+    protected void addServiced(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "addServiced", params, signature);
+    }
+    
+
+    /**
+     * Invoke the removeServiced method on the deployer.
+     */
+    protected void removeServiced(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "removeServiced", params, signature);
+    }
+    
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * subdirectories recursively. The code assumes that the directory exists.
+     *
+     * @param dir File object representing the directory to be deleted.
+     */
+    protected boolean undeployDir(File dir) {
+
+        String files[] = dir.list();
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; i < files.length; i++) {
+            File file = new File(dir, files[i]);
+            if (file.isDirectory()) {
+                if (!undeployDir(file)) {
+                    return false;
+                }
+            } else {
+                if (!file.delete()) {
+                    return false;
+                }
+            }
+        }
+        return dir.delete();
+    }
+
+
+    /**
+     * Upload the WAR file included in this request, and store it at the
+     * specified file location.
+     *
+     * @param writer    Writer to render to
+     * @param request   The servlet request we are processing
+     * @param war       The file into which we should store the uploaded WAR
+     * @param smClient  The StringManager used to construct i18n messages based
+     *                  on the Locale of the client
+     *
+     * @exception IOException if an I/O error occurs during processing
+     */
+    protected void uploadWar(PrintWriter writer, HttpServletRequest request,
+            File war, StringManager smClient) throws IOException {
+
+        if (war.exists() && !war.delete()) {
+            String msg = smClient.getString("managerServlet.deleteFail", war);
+            throw new IOException(msg);
+        }
+        ServletInputStream istream = null;
+        BufferedOutputStream ostream = null;
+        try {
+            istream = request.getInputStream();
+            ostream =
+                new BufferedOutputStream(new FileOutputStream(war), 1024);
+            byte buffer[] = new byte[1024];
+            while (true) {
+                int n = istream.read(buffer);
+                if (n < 0) {
+                    break;
+                }
+                ostream.write(buffer, 0, n);
+            }
+            ostream.flush();
+            ostream.close();
+            ostream = null;
+            istream.close();
+            istream = null;
+        } catch (IOException e) {
+            if (war.exists() && !war.delete()) {
+                writer.println(
+                        smClient.getString("managerServlet.deleteFail", war));
+            }
+            throw e;
+        } finally {
+            if (ostream != null) {
+                try {
+                    ostream.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+                ostream = null;
+            }
+            if (istream != null) {
+                try {
+                    istream.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+                istream = null;
+            }
+        }
+
+    }
+
+
+    protected StringManager getStringManager(HttpServletRequest req) {
+        Enumeration<Locale> requestedLocales = req.getLocales();
+        while (requestedLocales.hasMoreElements()) {
+            Locale locale = requestedLocales.nextElement();
+            StringManager result = StringManager.getManager(Constants.Package,
+                    locale);
+            if (result.getLocale().equals(locale)) {
+                return result;
+            }
+        }
+        // Return the default
+        return sm;
+    }
+
+
+    protected static boolean validateContextName(ContextName cn,
+            PrintWriter writer, StringManager sm) {
+        
+        // ContextName should be non-null with a path that is empty or starts
+        // with /
+        if (cn != null &&
+                (cn.getPath().startsWith("/") || cn.getPath().equals(""))) {
+            return true;
+        }
+        
+        String path = null;
+        if (cn != null) {
+            path = RequestUtil.filter(cn.getPath());
+        }
+        writer.println(sm.getString("managerServlet.invalidPath", path));
+        return false;
+    }
+
+    /**
+     * Copy the specified file or directory to the destination.
+     *
+     * @param src File object representing the source
+     * @param dest File object representing the destination
+     */
+    public static boolean copy(File src, File dest) {
+        boolean result = false;
+        try {
+            if( src != null &&
+                    !src.getCanonicalPath().equals(dest.getCanonicalPath()) ) {
+                result = copyInternal(src, dest, new byte[4096]);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    
+    /**
+     * Copy the specified file or directory to the destination.
+     *
+     * @param src File object representing the source
+     * @param dest File object representing the destination
+     */
+    public static boolean copyInternal(File src, File dest, byte[] buf) {
+        
+        boolean result = true;
+        
+        String files[] = null;
+        if (src.isDirectory()) {
+            files = src.list();
+            result = dest.mkdir();
+        } else {
+            files = new String[1];
+            files[0] = "";
+        }
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; (i < files.length) && result; i++) {
+            File fileSrc = new File(src, files[i]);
+            File fileDest = new File(dest, files[i]);
+            if (fileSrc.isDirectory()) {
+                result = copyInternal(fileSrc, fileDest, buf);
+            } else {
+                FileInputStream is = null;
+                FileOutputStream os = null;
+                try {
+                    is = new FileInputStream(fileSrc);
+                    os = new FileOutputStream(fileDest);
+                    int len = 0;
+                    while (true) {
+                        len = is.read(buf);
+                        if (len == -1)
+                            break;
+                        os.write(buf, 0, len);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    result = false;
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                    if (os != null) {
+                        try {
+                            os.close();
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+        
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/StatusManagerServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/StatusManagerServlet.java
new file mode 100644
index 0000000..88210c7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/StatusManagerServlet.java
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.manager;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerNotification;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.util.ServerInfo;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * This servlet will display a complete status of the HTTP/1.1 connector.
+ *
+ * @author Remy Maucherat
+ * @version $Id: StatusManagerServlet.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+
+public class StatusManagerServlet
+    extends HttpServlet implements NotificationListener {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------- Instance Variables
+    /**
+     * MBean server.
+     */
+    protected MBeanServer mBeanServer = null;
+
+
+    /**
+     * Vector of protocol handlers object names.
+     */
+    protected Vector<ObjectName> protocolHandlers = new Vector<ObjectName>();
+
+
+    /**
+     * Vector of thread pools object names.
+     */
+    protected Vector<ObjectName> threadPools = new Vector<ObjectName>();
+
+
+    /**
+     * Vector of request processors object names.
+     */
+    protected Vector<ObjectName> requestProcessors = new Vector<ObjectName>();
+
+
+    /**
+     * Vector of global request processors object names.
+     */
+    protected Vector<ObjectName> globalRequestProcessors = new Vector<ObjectName>();
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Initialize this servlet.
+     */
+    @Override
+    public void init() throws ServletException {
+
+        // Retrieve the MBean server
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+
+        try {
+
+            // Query protocol handlers
+            String onStr = "*:type=ProtocolHandler,*";
+            ObjectName objectName = new ObjectName(onStr);
+            Set<ObjectInstance> set = mBeanServer.queryMBeans(objectName, null);
+            Iterator<ObjectInstance> iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = iterator.next();
+                protocolHandlers.addElement(oi.getObjectName());
+            }
+
+            // Query Thread Pools
+            onStr = "*:type=ThreadPool,*";
+            objectName = new ObjectName(onStr);
+            set = mBeanServer.queryMBeans(objectName, null);
+            iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = iterator.next();
+                threadPools.addElement(oi.getObjectName());
+            }
+
+            // Query Global Request Processors
+            onStr = "*:type=GlobalRequestProcessor,*";
+            objectName = new ObjectName(onStr);
+            set = mBeanServer.queryMBeans(objectName, null);
+            iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = iterator.next();
+                globalRequestProcessors.addElement(oi.getObjectName());
+            }
+
+            // Query Request Processors
+            onStr = "*:type=RequestProcessor,*";
+            objectName = new ObjectName(onStr);
+            set = mBeanServer.queryMBeans(objectName, null);
+            iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = iterator.next();
+                requestProcessors.addElement(oi.getObjectName());
+            }
+
+            // Register with MBean server
+            onStr = "JMImplementation:type=MBeanServerDelegate";
+            objectName = new ObjectName(onStr);
+            mBeanServer.addNotificationListener(objectName, this, null, null);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+    /**
+     * Finalize this servlet.
+     */
+    @Override
+    public void destroy() {
+
+        // No actions necessary
+
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // mode is flag for HTML or XML output
+        int mode = 0;
+        // if ?XML=true, set the mode to XML
+        if (request.getParameter("XML") != null 
+            && request.getParameter("XML").equals("true")) {
+            mode = 1;
+        }
+        StatusTransformer.setContentType(response, mode);
+
+        PrintWriter writer = response.getWriter();
+
+        boolean completeStatus = false;
+        if ((request.getPathInfo() != null) 
+            && (request.getPathInfo().equals("/all"))) {
+            completeStatus = true;
+        }
+        // use StatusTransformer to output status
+        StatusTransformer.writeHeader(writer,mode);
+
+        // Body Header Section
+        Object[] args = new Object[2];
+        args[0] = request.getContextPath();
+        if (completeStatus) {
+            args[1] = sm.getString("statusServlet.complete");
+        } else {
+            args[1] = sm.getString("statusServlet.title");
+        }
+        // use StatusTransformer to output status
+        StatusTransformer.writeBody(writer,args,mode);
+
+        // Manager Section
+        args = new Object[9];
+        args[0] = sm.getString("htmlManagerServlet.manager");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/list");
+        args[2] = sm.getString("htmlManagerServlet.list");
+        args[3] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlManagerServlet.helpHtmlManagerFile"));
+        args[4] = sm.getString("htmlManagerServlet.helpHtmlManager");
+        args[5] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlManagerServlet.helpManagerFile"));
+        args[6] = sm.getString("htmlManagerServlet.helpManager");
+        if (completeStatus) {
+            args[7] = response.encodeURL
+                (request.getContextPath() + "/status");
+            args[8] = sm.getString("statusServlet.title");
+        } else {
+            args[7] = response.encodeURL
+                (request.getContextPath() + "/status/all");
+            args[8] = sm.getString("statusServlet.complete");
+        }
+        // use StatusTransformer to output status
+        StatusTransformer.writeManager(writer,args,mode);
+
+        // Server Header Section
+        args = new Object[7];
+        args[0] = sm.getString("htmlManagerServlet.serverTitle");
+        args[1] = sm.getString("htmlManagerServlet.serverVersion");
+        args[2] = sm.getString("htmlManagerServlet.serverJVMVersion");
+        args[3] = sm.getString("htmlManagerServlet.serverJVMVendor");
+        args[4] = sm.getString("htmlManagerServlet.serverOSName");
+        args[5] = sm.getString("htmlManagerServlet.serverOSVersion");
+        args[6] = sm.getString("htmlManagerServlet.serverOSArch");
+        // use StatusTransformer to output status
+        StatusTransformer.writePageHeading(writer,args,mode);
+
+        // Server Row Section
+        args = new Object[6];
+        args[0] = ServerInfo.getServerInfo();
+        args[1] = System.getProperty("java.runtime.version");
+        args[2] = System.getProperty("java.vm.vendor");
+        args[3] = System.getProperty("os.name");
+        args[4] = System.getProperty("os.version");
+        args[5] = System.getProperty("os.arch");
+        // use StatusTransformer to output status
+        StatusTransformer.writeServerInfo(writer, args, mode);
+
+        try {
+
+            // Display operating system statistics using APR if available
+            StatusTransformer.writeOSState(writer,mode);
+
+            // Display virtual machine statistics
+            StatusTransformer.writeVMState(writer,mode);
+
+            Enumeration<ObjectName> enumeration = threadPools.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = enumeration.nextElement();
+                String name = objectName.getKeyProperty("name");
+                // use StatusTransformer to output status
+                StatusTransformer.writeConnectorState
+                    (writer, objectName,
+                     name, mBeanServer, globalRequestProcessors,
+                     requestProcessors, mode);
+            }
+
+            if ((request.getPathInfo() != null) 
+                && (request.getPathInfo().equals("/all"))) {
+                // Note: Retrieving the full status is much slower
+                // use StatusTransformer to output status
+                StatusTransformer.writeDetailedState
+                    (writer, mBeanServer, mode);
+            }
+
+        } catch (Exception e) {
+            throw new ServletException(e);
+        }
+
+        // use StatusTransformer to output status
+        StatusTransformer.writeFooter(writer, mode);
+
+    }
+
+    // ------------------------------------------- NotificationListener Methods
+
+
+    @Override
+    public void handleNotification(Notification notification,
+                                   java.lang.Object handback) {
+
+        if (notification instanceof MBeanServerNotification) {
+            ObjectName objectName = 
+                ((MBeanServerNotification) notification).getMBeanName();
+            if (notification.getType().equals
+                (MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
+                String type = objectName.getKeyProperty("type");
+                if (type != null) {
+                    if (type.equals("ProtocolHandler")) {
+                        protocolHandlers.addElement(objectName);
+                    } else if (type.equals("ThreadPool")) {
+                        threadPools.addElement(objectName);
+                    } else if (type.equals("GlobalRequestProcessor")) {
+                        globalRequestProcessors.addElement(objectName);
+                    } else if (type.equals("RequestProcessor")) {
+                        requestProcessors.addElement(objectName);
+                    }
+                }
+            } else if (notification.getType().equals
+                       (MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
+                String type = objectName.getKeyProperty("type");
+                if (type != null) {
+                    if (type.equals("ProtocolHandler")) {
+                        protocolHandlers.removeElement(objectName);
+                    } else if (type.equals("ThreadPool")) {
+                        threadPools.removeElement(objectName);
+                    } else if (type.equals("GlobalRequestProcessor")) {
+                        globalRequestProcessors.removeElement(objectName);
+                    } else if (type.equals("RequestProcessor")) {
+                        requestProcessors.removeElement(objectName);
+                    }
+                }
+                String j2eeType = objectName.getKeyProperty("j2eeType");
+                if (j2eeType != null) {
+                    
+                }
+            }
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/StatusTransformer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/StatusTransformer.java
new file mode 100644
index 0000000..b641905
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/StatusTransformer.java
@@ -0,0 +1,938 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.manager;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.util.RequestUtil;
+import org.apache.tomcat.util.ExceptionUtils;
+
+/**
+ * This is a refactoring of the servlet to externalize
+ * the output into a simple class. Although we could
+ * use XSLT, that is unnecessarily complex.
+ *
+ * @author Peter Lin
+ * @version $Id: StatusTransformer.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+
+public class StatusTransformer {
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    public static void setContentType(HttpServletResponse response, 
+                                      int mode) {
+        if (mode == 0){
+            response.setContentType("text/html;charset="+Constants.CHARSET);
+        } else if (mode == 1){
+            response.setContentType("text/xml;charset="+Constants.CHARSET);
+        }
+    }
+
+
+    /**
+     * Write an HTML or XML header.
+     *
+     * @param writer the PrintWriter to use
+     * @param mode - 0 = HTML header, 1 = XML declaration
+     *
+     */
+    public static void writeHeader(PrintWriter writer, int mode) {
+        if (mode == 0){
+            // HTML Header Section
+            writer.print(Constants.HTML_HEADER_SECTION);
+        } else if (mode == 1){
+            writer.write(Constants.XML_DECLARATION);
+            writer.write
+                (Constants.XML_STYLE);
+            writer.write("<status>");
+        }
+    }
+
+
+    /**
+     * Write the header body. XML output doesn't bother
+     * to output this stuff, since it's just title.
+     * 
+     * @param writer The output writer
+     * @param args What to write
+     * @param mode 0 means write 
+     */
+    public static void writeBody(PrintWriter writer, Object[] args, int mode) {
+        if (mode == 0){
+            writer.print(MessageFormat.format
+                         (Constants.BODY_HEADER_SECTION, args));
+        }
+    }
+
+
+    /**
+     * Write the manager webapp information.
+     * 
+     * @param writer The output writer
+     * @param args What to write
+     * @param mode 0 means write
+     */
+    public static void writeManager(PrintWriter writer, Object[] args, 
+                                    int mode) {
+        if (mode == 0){
+            writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
+        }
+    }
+
+
+    public static void writePageHeading(PrintWriter writer, Object[] args, 
+                                        int mode) {
+        if (mode == 0){
+            writer.print(MessageFormat.format
+                         (Constants.SERVER_HEADER_SECTION, args));
+        }
+    }
+
+
+    public static void writeServerInfo(PrintWriter writer, Object[] args, 
+                                       int mode){
+        if (mode == 0){
+            writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));
+        }
+    }
+
+
+    /**
+     * 
+     */
+    public static void writeFooter(PrintWriter writer, int mode) {
+        if (mode == 0){
+            // HTML Tail Section
+            writer.print(Constants.HTML_TAIL_SECTION);
+        } else if (mode == 1){
+            writer.write("</status>");
+        }
+    }
+
+
+    /**
+     * Write the OS state. Mode 0 will generate HTML.
+     * Mode 1 will generate XML.
+     */
+    public static void writeOSState(PrintWriter writer, int mode) {
+        long[] result = new long[16];
+        boolean ok = false;
+        try {
+            String methodName = "info";
+            Class<?> paramTypes[] = new Class[1];
+            paramTypes[0] = result.getClass();
+            Object paramValues[] = new Object[1];
+            paramValues[0] = result;
+            Method method = Class.forName("org.apache.tomcat.jni.OS")
+                .getMethod(methodName, paramTypes);
+            method.invoke(null, paramValues);
+            ok = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+        }
+        
+        if (ok) {
+            if (mode == 0){
+                writer.print("<h1>OS</h1>");
+
+                writer.print("<p>");
+                writer.print(" Physical memory: ");
+                writer.print(formatSize(Long.valueOf(result[0]), true));
+                writer.print(" Available memory: ");
+                writer.print(formatSize(Long.valueOf(result[1]), true));
+                writer.print(" Total page file: ");
+                writer.print(formatSize(Long.valueOf(result[2]), true));
+                writer.print(" Free page file: ");
+                writer.print(formatSize(Long.valueOf(result[3]), true));
+                writer.print(" Memory load: ");
+                writer.print(Long.valueOf(result[6]));
+                writer.print("<br>");
+                writer.print(" Process kernel time: ");
+                writer.print(formatTime(Long.valueOf(result[11] / 1000), true));
+                writer.print(" Process user time: ");
+                writer.print(formatTime(Long.valueOf(result[12] / 1000), true));
+                writer.print("</p>");
+            } else if (mode == 1){
+                // NO-OP
+            }
+        }
+        
+    }
+    
+    
+    /**
+     * Write the VM state. Mode 0 will generate HTML.
+     * Mode 1 will generate XML.
+     */
+    public static void writeVMState(PrintWriter writer, int mode)
+        throws Exception {
+
+        if (mode == 0){
+            writer.print("<h1>JVM</h1>");
+
+            writer.print("<p>");
+            writer.print(" Free memory: ");
+            writer.print(formatSize(
+                    Long.valueOf(Runtime.getRuntime().freeMemory()), true));
+            writer.print(" Total memory: ");
+            writer.print(formatSize(
+                    Long.valueOf(Runtime.getRuntime().totalMemory()), true));
+            writer.print(" Max memory: ");
+            writer.print(formatSize(
+                    Long.valueOf(Runtime.getRuntime().maxMemory()), true));
+            writer.print("</p>");
+        } else if (mode == 1){
+            writer.write("<jvm>");
+
+            writer.write("<memory");
+            writer.write(" free='" + Runtime.getRuntime().freeMemory() + "'");
+            writer.write(" total='" + Runtime.getRuntime().totalMemory() + "'");
+            writer.write(" max='" + Runtime.getRuntime().maxMemory() + "'/>");
+
+            writer.write("</jvm>");
+        }
+
+    }
+
+
+    /**
+     * Write connector state.
+     */
+    public static void writeConnectorState(PrintWriter writer,
+            ObjectName tpName, String name, MBeanServer mBeanServer,
+            Vector<ObjectName> globalRequestProcessors,
+            Vector<ObjectName> requestProcessors, int mode) throws Exception {
+
+        if (mode == 0) {
+            writer.print("<h1>");
+            writer.print(name);
+            writer.print("</h1>");
+
+            writer.print("<p>");
+            writer.print(" Max threads: ");
+            writer.print(mBeanServer.getAttribute(tpName, "maxThreads"));
+            writer.print(" Current thread count: ");
+            writer.print(mBeanServer.getAttribute(tpName, "currentThreadCount"));
+            writer.print(" Current thread busy: ");
+            writer.print(mBeanServer.getAttribute(tpName, "currentThreadsBusy"));
+            try {
+                Object value = mBeanServer.getAttribute(tpName, "keepAliveCount");
+                writer.print(" Keeped alive sockets count: ");
+                writer.print(value);
+            } catch (Exception e) {
+                // Ignore
+            }
+            
+            writer.print("<br>");
+
+            ObjectName grpName = null;
+
+            Enumeration<ObjectName> enumeration =
+                globalRequestProcessors.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = enumeration.nextElement();
+                if (name.equals(objectName.getKeyProperty("name"))) {
+                    grpName = objectName;
+                }
+            }
+
+            if (grpName == null) {
+                return;
+            }
+
+            writer.print(" Max processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (grpName, "maxTime"), false));
+            writer.print(" Processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (grpName, "processingTime"), true));
+            writer.print(" Request count: ");
+            writer.print(mBeanServer.getAttribute(grpName, "requestCount"));
+            writer.print(" Error count: ");
+            writer.print(mBeanServer.getAttribute(grpName, "errorCount"));
+            writer.print(" Bytes received: ");
+            writer.print(formatSize(mBeanServer.getAttribute
+                                    (grpName, "bytesReceived"), true));
+            writer.print(" Bytes sent: ");
+            writer.print(formatSize(mBeanServer.getAttribute
+                                    (grpName, "bytesSent"), true));
+            writer.print("</p>");
+
+            writer.print("<table border=\"0\"><tr><th>Stage</th><th>Time</th><th>B Sent</th><th>B Recv</th><th>Client</th><th>VHost</th><th>Request</th></tr>");
+
+            enumeration = requestProcessors.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = enumeration.nextElement();
+                if (name.equals(objectName.getKeyProperty("worker"))) {
+                    writer.print("<tr>");
+                    writeProcessorState(writer, objectName, mBeanServer, mode);
+                    writer.print("</tr>");
+                }
+            }
+
+            writer.print("</table>");
+
+            writer.print("<p>");
+            writer.print("P: Parse and prepare request S: Service F: Finishing R: Ready K: Keepalive");
+            writer.print("</p>");
+        } else if (mode == 1){
+            writer.write("<connector name='" + name + "'>");
+
+            writer.write("<threadInfo ");
+            writer.write(" maxThreads=\"" + mBeanServer.getAttribute(tpName, "maxThreads") + "\"");
+            writer.write(" currentThreadCount=\"" + mBeanServer.getAttribute(tpName, "currentThreadCount") + "\"");
+            writer.write(" currentThreadsBusy=\"" + mBeanServer.getAttribute(tpName, "currentThreadsBusy") + "\"");
+            writer.write(" />");
+
+            ObjectName grpName = null;
+
+            Enumeration<ObjectName> enumeration =
+                globalRequestProcessors.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = enumeration.nextElement();
+                if (name.equals(objectName.getKeyProperty("name"))) {
+                    grpName = objectName;
+                }
+            }
+
+            if (grpName != null) {
+
+                writer.write("<requestInfo ");
+                writer.write(" maxTime=\"" + mBeanServer.getAttribute(grpName, "maxTime") + "\"");
+                writer.write(" processingTime=\"" + mBeanServer.getAttribute(grpName, "processingTime") + "\"");
+                writer.write(" requestCount=\"" + mBeanServer.getAttribute(grpName, "requestCount") + "\"");
+                writer.write(" errorCount=\"" + mBeanServer.getAttribute(grpName, "errorCount") + "\"");
+                writer.write(" bytesReceived=\"" + mBeanServer.getAttribute(grpName, "bytesReceived") + "\"");
+                writer.write(" bytesSent=\"" + mBeanServer.getAttribute(grpName, "bytesSent") + "\"");
+                writer.write(" />");
+
+                writer.write("<workers>");
+                enumeration = requestProcessors.elements();
+                while (enumeration.hasMoreElements()) {
+                    ObjectName objectName = enumeration.nextElement();
+                    if (name.equals(objectName.getKeyProperty("worker"))) {
+                        writeProcessorState(writer, objectName, mBeanServer, mode);
+                    }
+                }
+                writer.write("</workers>");
+            }
+
+            writer.write("</connector>");
+        }
+
+    }
+
+
+    /**
+     * Write processor state.
+     */
+    protected static void writeProcessorState(PrintWriter writer, 
+                                              ObjectName pName,
+                                              MBeanServer mBeanServer, 
+                                              int mode)
+        throws Exception {
+
+        Integer stageValue = 
+            (Integer) mBeanServer.getAttribute(pName, "stage");
+        int stage = stageValue.intValue();
+        boolean fullStatus = true;
+        boolean showRequest = true;
+        String stageStr = null;
+
+        switch (stage) {
+
+        case (1/*org.apache.coyote.Constants.STAGE_PARSE*/):
+            stageStr = "P";
+            fullStatus = false;
+            break;
+        case (2/*org.apache.coyote.Constants.STAGE_PREPARE*/):
+            stageStr = "P";
+            fullStatus = false;
+            break;
+        case (3/*org.apache.coyote.Constants.STAGE_SERVICE*/):
+            stageStr = "S";
+            break;
+        case (4/*org.apache.coyote.Constants.STAGE_ENDINPUT*/):
+            stageStr = "F";
+            break;
+        case (5/*org.apache.coyote.Constants.STAGE_ENDOUTPUT*/):
+            stageStr = "F";
+            break;
+        case (7/*org.apache.coyote.Constants.STAGE_ENDED*/):
+            stageStr = "R";
+            fullStatus = false;
+            break;
+        case (6/*org.apache.coyote.Constants.STAGE_KEEPALIVE*/):
+            stageStr = "K";
+            fullStatus = true;
+            showRequest = false;
+            break;
+        case (0/*org.apache.coyote.Constants.STAGE_NEW*/):
+            stageStr = "R";
+            fullStatus = false;
+            break;
+        default:
+            // Unknown stage
+            stageStr = "?";
+            fullStatus = false;
+
+        }
+
+        if (mode == 0) {
+            writer.write("<td><strong>");
+            writer.write(stageStr);
+            writer.write("</strong></td>");
+
+            if (fullStatus) {
+                writer.write("<td>");
+                writer.print(formatTime(mBeanServer.getAttribute
+                                        (pName, "requestProcessingTime"), false));
+                writer.write("</td>");
+                writer.write("<td>");
+                if (showRequest) {
+                    writer.print(formatSize(mBeanServer.getAttribute
+                                            (pName, "requestBytesSent"), false));
+                } else {
+                    writer.write("?");
+                }
+                writer.write("</td>");
+                writer.write("<td>");
+                if (showRequest) {
+                    writer.print(formatSize(mBeanServer.getAttribute
+                                            (pName, "requestBytesReceived"), 
+                                            false));
+                } else {
+                    writer.write("?");
+                }
+                writer.write("</td>");
+                writer.write("<td>");
+                writer.print(filter(mBeanServer.getAttribute
+                                    (pName, "remoteAddr")));
+                writer.write("</td>");
+                writer.write("<td nowrap>");
+                writer.write(filter(mBeanServer.getAttribute
+                                    (pName, "virtualHost")));
+                writer.write("</td>");
+                writer.write("<td nowrap>");
+                if (showRequest) {
+                    writer.write(filter(mBeanServer.getAttribute
+                                        (pName, "method")));
+                    writer.write(" ");
+                    writer.write(filter(mBeanServer.getAttribute
+                                        (pName, "currentUri")));
+                    String queryString = (String) mBeanServer.getAttribute
+                        (pName, "currentQueryString");
+                    if ((queryString != null) && (!queryString.equals(""))) {
+                        writer.write("?");
+                        writer.print(RequestUtil.filter(queryString));
+                    }
+                    writer.write(" ");
+                    writer.write(filter(mBeanServer.getAttribute
+                                        (pName, "protocol")));
+                } else {
+                    writer.write("?");
+                }
+                writer.write("</td>");
+            } else {
+                writer.write("<td>?</td><td>?</td><td>?</td><td>?</td><td>?</td><td>?</td>");
+            }
+        } else if (mode == 1){
+            writer.write("<worker ");
+            writer.write(" stage=\"" + stageStr + "\"");
+
+            if (fullStatus) {
+                writer.write(" requestProcessingTime=\"" 
+                             + mBeanServer.getAttribute
+                             (pName, "requestProcessingTime") + "\"");
+                writer.write(" requestBytesSent=\"");
+                if (showRequest) {
+                    writer.write("" + mBeanServer.getAttribute
+                                 (pName, "requestBytesSent"));
+                } else {
+                    writer.write("0");
+                }
+                writer.write("\"");
+                writer.write(" requestBytesReceived=\"");
+                if (showRequest) {
+                    writer.write("" + mBeanServer.getAttribute
+                                 (pName, "requestBytesReceived"));
+                } else {
+                    writer.write("0");
+                }
+                writer.write("\"");
+                writer.write(" remoteAddr=\"" 
+                             + filter(mBeanServer.getAttribute
+                                      (pName, "remoteAddr")) + "\"");
+                writer.write(" virtualHost=\"" 
+                             + filter(mBeanServer.getAttribute
+                                      (pName, "virtualHost")) + "\"");
+
+                if (showRequest) {
+                    writer.write(" method=\"" 
+                                 + filter(mBeanServer.getAttribute
+                                          (pName, "method")) + "\"");
+                    writer.write(" currentUri=\"" 
+                                 + filter(mBeanServer.getAttribute
+                                          (pName, "currentUri")) + "\"");
+
+                    String queryString = (String) mBeanServer.getAttribute
+                        (pName, "currentQueryString");
+                    if ((queryString != null) && (!queryString.equals(""))) {
+                        writer.write(" currentQueryString=\"" 
+                                     + RequestUtil.filter(queryString) + "\"");
+                    } else {
+                        writer.write(" currentQueryString=\"&#63;\"");
+                    }
+                    writer.write(" protocol=\"" 
+                                 + filter(mBeanServer.getAttribute
+                                          (pName, "protocol")) + "\"");
+                } else {
+                    writer.write(" method=\"&#63;\"");
+                    writer.write(" currentUri=\"&#63;\"");
+                    writer.write(" currentQueryString=\"&#63;\"");
+                    writer.write(" protocol=\"&#63;\"");
+                }
+            } else {
+                writer.write(" requestProcessingTime=\"0\"");
+                writer.write(" requestBytesSent=\"0\"");
+                writer.write(" requestBytesRecieved=\"0\"");
+                writer.write(" remoteAddr=\"&#63;\"");
+                writer.write(" virtualHost=\"&#63;\"");
+                writer.write(" method=\"&#63;\"");
+                writer.write(" currentUri=\"&#63;\"");
+                writer.write(" currentQueryString=\"&#63;\"");
+                writer.write(" protocol=\"&#63;\"");
+            }
+            writer.write(" />");
+        }
+
+    }
+
+
+    /**
+     * Write applications state.
+     */
+    public static void writeDetailedState(PrintWriter writer,
+                                          MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0){
+            ObjectName queryHosts = new ObjectName("*:j2eeType=WebModule,*");
+            Set<ObjectName> hostsON = mBeanServer.queryNames(queryHosts, null);
+
+            // Navigation menu
+            writer.print("<h1>");
+            writer.print("Application list");
+            writer.print("</h1>");
+
+            writer.print("<p>");
+            int count = 0;
+            Iterator<ObjectName> iterator = hostsON.iterator();
+            while (iterator.hasNext()) {
+                ObjectName contextON = iterator.next();
+                String webModuleName = contextON.getKeyProperty("name");
+                if (webModuleName.startsWith("//")) {
+                    webModuleName = webModuleName.substring(2);
+                }
+                int slash = webModuleName.indexOf("/");
+                if (slash == -1) {
+                    count++;
+                    continue;
+                }
+
+                writer.print("<a href=\"#" + (count++) + ".0\">");
+                writer.print(filter(webModuleName));
+                writer.print("</a>");
+                if (iterator.hasNext()) {
+                    writer.print("<br>");
+                }
+
+            }
+            writer.print("</p>");
+
+            // Webapp list
+            count = 0;
+            iterator = hostsON.iterator();
+            while (iterator.hasNext()) {
+                ObjectName contextON = iterator.next();
+                writer.print("<a class=\"A.name\" name=\"" 
+                             + (count++) + ".0\">");
+                writeContext(writer, contextON, mBeanServer, mode);
+            }
+
+        } else if (mode == 1){
+            // for now we don't write out the Detailed state in XML
+        }
+
+    }
+
+
+    /**
+     * Write context state.
+     */
+    protected static void writeContext(PrintWriter writer, 
+                                       ObjectName objectName,
+                                       MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0){
+            String webModuleName = objectName.getKeyProperty("name");
+            String name = webModuleName;
+            if (name == null) {
+                return;
+            }
+            
+            String hostName = null;
+            String contextName = null;
+            if (name.startsWith("//")) {
+                name = name.substring(2);
+            }
+            int slash = name.indexOf("/");
+            if (slash != -1) {
+                hostName = name.substring(0, slash);
+                contextName = name.substring(slash);
+            } else {
+                return;
+            }
+
+            ObjectName queryManager = new ObjectName
+                (objectName.getDomain() + ":type=Manager,context=" + contextName 
+                 + ",host=" + hostName + ",*");
+            Set<ObjectName> managersON =
+                mBeanServer.queryNames(queryManager, null);
+            ObjectName managerON = null;
+            Iterator<ObjectName> iterator2 = managersON.iterator();
+            while (iterator2.hasNext()) {
+                managerON = iterator2.next();
+            }
+
+            ObjectName queryJspMonitor = new ObjectName
+                (objectName.getDomain() + ":type=JspMonitor,WebModule=" +
+                 webModuleName + ",*");
+            Set<ObjectName> jspMonitorONs =
+                mBeanServer.queryNames(queryJspMonitor, null);
+
+            // Special case for the root context
+            if (contextName.equals("/")) {
+                contextName = "";
+            }
+
+            writer.print("<h1>");
+            writer.print(filter(name));
+            writer.print("</h1>");
+            writer.print("</a>");
+
+            writer.print("<p>");
+            Object startTime = mBeanServer.getAttribute(objectName,
+                                                        "startTime");
+            writer.print(" Start time: " +
+                         new Date(((Long) startTime).longValue()));
+            writer.print(" Startup time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "startupTime"), false));
+            writer.print(" TLD scan time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "tldScanTime"), false));
+            if (managerON != null) {
+                writeManager(writer, managerON, mBeanServer, mode);
+            }
+            if (jspMonitorONs != null) {
+                writeJspMonitor(writer, jspMonitorONs, mBeanServer, mode);
+            }
+            writer.print("</p>");
+
+            String onStr = objectName.getDomain() 
+                + ":j2eeType=Servlet,WebModule=" + webModuleName + ",*";
+            ObjectName servletObjectName = new ObjectName(onStr);
+            Set<ObjectInstance> set =
+                mBeanServer.queryMBeans(servletObjectName, null);
+            Iterator<ObjectInstance> iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = iterator.next();
+                writeWrapper(writer, oi.getObjectName(), mBeanServer, mode);
+            }
+
+        } else if (mode == 1){
+            // for now we don't write out the context in XML
+        }
+
+    }
+
+
+    /**
+     * Write detailed information about a manager.
+     */
+    public static void writeManager(PrintWriter writer, ObjectName objectName,
+                                    MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0) {
+            writer.print("<br>");
+            writer.print(" Active sessions: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "activeSessions"));
+            writer.print(" Session count: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "sessionCounter"));
+            writer.print(" Max active sessions: ");
+            writer.print(mBeanServer.getAttribute(objectName, "maxActive"));
+            writer.print(" Rejected session creations: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "rejectedSessions"));
+            writer.print(" Expired sessions: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "expiredSessions"));
+            writer.print(" Longest session alive time: ");
+            writer.print(formatSeconds(mBeanServer.getAttribute(
+                                                    objectName,
+                                                    "sessionMaxAliveTime")));
+            writer.print(" Average session alive time: ");
+            writer.print(formatSeconds(mBeanServer.getAttribute(
+                                                    objectName,
+                                                    "sessionAverageAliveTime")));
+            writer.print(" Processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "processingTime"), false));
+        } else if (mode == 1) {
+            // for now we don't write out the wrapper details
+        }
+
+    }
+
+
+    /**
+     * Write JSP monitoring information.
+     */
+    public static void writeJspMonitor(PrintWriter writer,
+                                       Set<ObjectName> jspMonitorONs,
+                                       MBeanServer mBeanServer,
+                                       int mode)
+            throws Exception {
+
+        int jspCount = 0;
+        int jspReloadCount = 0;
+
+        Iterator<ObjectName> iter = jspMonitorONs.iterator();
+        while (iter.hasNext()) {
+            ObjectName jspMonitorON = iter.next();
+            Object obj = mBeanServer.getAttribute(jspMonitorON, "jspCount");
+            jspCount += ((Integer) obj).intValue();
+            obj = mBeanServer.getAttribute(jspMonitorON, "jspReloadCount");
+            jspReloadCount += ((Integer) obj).intValue();
+        }
+
+        if (mode == 0) {
+            writer.print("<br>");
+            writer.print(" JSPs loaded: ");
+            writer.print(jspCount);
+            writer.print(" JSPs reloaded: ");
+            writer.print(jspReloadCount);
+        } else if (mode == 1) {
+            // for now we don't write out anything
+        }
+    }
+
+
+    /**
+     * Write detailed information about a wrapper.
+     */
+    public static void writeWrapper(PrintWriter writer, ObjectName objectName,
+                                    MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0) {
+            String servletName = objectName.getKeyProperty("name");
+            
+            String[] mappings = (String[]) 
+                mBeanServer.invoke(objectName, "findMappings", null, null);
+            
+            writer.print("<h2>");
+            writer.print(filter(servletName));
+            if ((mappings != null) && (mappings.length > 0)) {
+                writer.print(" [ ");
+                for (int i = 0; i < mappings.length; i++) {
+                    writer.print(filter(mappings[i]));
+                    if (i < mappings.length - 1) {
+                        writer.print(" , ");
+                    }
+                }
+                writer.print(" ] ");
+            }
+            writer.print("</h2>");
+            
+            writer.print("<p>");
+            writer.print(" Processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "processingTime"), true));
+            writer.print(" Max time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "maxTime"), false));
+            writer.print(" Request count: ");
+            writer.print(mBeanServer.getAttribute(objectName, "requestCount"));
+            writer.print(" Error count: ");
+            writer.print(mBeanServer.getAttribute(objectName, "errorCount"));
+            writer.print(" Load time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "loadTime"), false));
+            writer.print(" Classloading time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "classLoadTime"), false));
+            writer.print("</p>");
+        } else if (mode == 1){
+            // for now we don't write out the wrapper details
+        }
+
+    }
+
+
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param obj The message string to be filtered
+     */
+    public static String filter(Object obj) {
+
+        if (obj == null)
+            return ("?");
+        String message = obj.toString();
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+        StringBuilder result = new StringBuilder(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+
+    }
+
+
+    /**
+     * Display the given size in bytes, either as KB or MB.
+     *
+     * @param mb true to display megabytes, false for kilobytes
+     */
+    public static String formatSize(Object obj, boolean mb) {
+
+        long bytes = -1L;
+
+        if (obj instanceof Long) {
+            bytes = ((Long) obj).longValue();
+        } else if (obj instanceof Integer) {
+            bytes = ((Integer) obj).intValue();
+        }
+
+        if (mb) {
+            StringBuilder buff = new StringBuilder();
+            if (bytes < 0) {
+                buff.append('-');
+                bytes = -bytes;
+            }
+            long mbytes = bytes / (1024 * 1024);
+            long rest = 
+                ((bytes - (mbytes * (1024 * 1024))) * 100) / (1024 * 1024);
+            buff.append(mbytes).append('.');
+            if (rest < 10) {
+                buff.append('0');
+            }
+            buff.append(rest).append(" MB");
+            return buff.toString();
+        } else {
+            return ((bytes / 1024) + " KB");
+        }
+
+    }
+
+
+    /**
+     * Display the given time in ms, either as ms or s.
+     *
+     * @param seconds true to display seconds, false for milliseconds
+     */
+    public static String formatTime(Object obj, boolean seconds) {
+
+        long time = -1L;
+
+        if (obj instanceof Long) {
+            time = ((Long) obj).longValue();
+        } else if (obj instanceof Integer) {
+            time = ((Integer) obj).intValue();
+        }
+
+        if (seconds) {
+            return ((((float) time ) / 1000) + " s");
+        } else {
+            return (time + " ms");
+        }
+    }
+
+
+    /**
+     * Formats the given time (given in seconds) as a string.
+     *
+     * @param obj Time object to be formatted as string
+     *
+     * @return String formatted time
+     */
+    public static String formatSeconds(Object obj) {
+
+        long time = -1L;
+
+        if (obj instanceof Long) {
+            time = ((Long) obj).longValue();
+        } else if (obj instanceof Integer) {
+            time = ((Integer) obj).intValue();
+        }
+
+        return (time + " s");
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/BaseSessionComparator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/BaseSessionComparator.java
new file mode 100644
index 0000000..ffc5c64
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/BaseSessionComparator.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager.util;
+
+import java.util.Comparator;
+
+import org.apache.catalina.Session;
+
+/**
+ * Comparator which permits to compare on a session's content
+ * @author C&eacute;drik LIME
+ */
+public abstract class BaseSessionComparator<T> implements Comparator<Session> {
+
+    /**
+     * 
+     */
+    public BaseSessionComparator() {
+        super();
+    }
+
+    public abstract Comparable<T> getComparableObject(Session session);
+
+    /* (non-Javadoc)
+     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public final int compare(Session s1, Session s2) {
+        Comparable<T> c1 = getComparableObject(s1);
+        Comparable<T> c2 = getComparableObject(s2);
+        return c1==null ? (c2==null ? 0 : -1) : (c2==null ? 1 : c1.compareTo((T)c2));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/ReverseComparator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/ReverseComparator.java
new file mode 100644
index 0000000..b699ef8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/ReverseComparator.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager.util;
+
+import java.util.Comparator;
+
+import org.apache.catalina.Session;
+
+/**
+ * Comparator which reverse the sort order
+ * @author C&eacute;drik LIME
+ */
+public class ReverseComparator implements Comparator<Session> {
+    protected Comparator<Session> comparator;
+
+    /**
+     * 
+     */
+    public ReverseComparator(Comparator<Session> comparator) {
+        super();
+        this.comparator = comparator;
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public int compare(Session o1, Session o2) {
+        int returnValue = comparator.compare(o1, o2);
+        return (- returnValue);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/SessionUtils.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/SessionUtils.java
new file mode 100644
index 0000000..e6398e9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/util/SessionUtils.java
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager.util;
+
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Session;
+
+/**
+ * Utility methods on HttpSessions...
+ * @author C&eacute;drik LIME
+ */
+public class SessionUtils {
+
+    /**
+     *
+     */
+    private SessionUtils() {
+        super();
+    }
+
+    /**
+     * The session attributes key under which the user's selected
+     * <code>java.util.Locale</code> is stored, if any.
+     */
+    // org.apache.struts.Globals.LOCALE_KEY
+    private static final String STRUTS_LOCALE_KEY = "org.apache.struts.action.LOCALE";//$NON-NLS-1$
+    // javax.servlet.jsp.jstl.core.Config.FMT_LOCALE
+    private static final String JSTL_LOCALE_KEY   = "javax.servlet.jsp.jstl.fmt.locale";//$NON-NLS-1$
+    // org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME
+    private static final String SPRING_LOCALE_KEY = "org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE";//$NON-NLS-1$
+    /**
+     * Lower and upper-case strings will be dynamically generated. Put mid-capitalised strings here!
+     */
+    private static final String[] LOCALE_TEST_ATTRIBUTES = new String[] {
+        STRUTS_LOCALE_KEY, SPRING_LOCALE_KEY, JSTL_LOCALE_KEY, "Locale", "java.util.Locale" };
+    /**
+     * For efficient operation, list the attributes here with the typically used
+     * capitalisation. This will be tried first and then the auto-generated
+     * upper and lower case versions will be tried.
+     */
+    private static final String[] USER_TEST_ATTRIBUTES = new String[] {
+        "Login", "User", "userName", "UserName", "Utilisateur",
+        "SPRING_SECURITY_LAST_USERNAME"};
+
+    /**
+     * Try to get user locale from the session, if possible.
+     * IMPLEMENTATION NOTE: this method has explicit support for Tapestry 3, Struts 1.x and Spring
+     * JSF check the browser meta tag "accept languages" to choose what language to display.
+     * @param in_session
+     * @return String
+     */
+    public static Locale guessLocaleFromSession(final Session in_session) {
+        return guessLocaleFromSession(in_session.getSession());
+    }
+    public static Locale guessLocaleFromSession(final HttpSession in_session) {
+        if (null == in_session) {
+            return null;
+        }
+        try {
+            Locale locale = null;
+
+            // First search "known locations"
+            for (int i = 0; i < LOCALE_TEST_ATTRIBUTES.length; ++i) {
+                Object obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i]);
+                if (null != obj && obj instanceof Locale) {
+                    locale = (Locale) obj;
+                    break;
+                }
+                obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i].toLowerCase(Locale.ENGLISH));
+                if (null != obj && obj instanceof Locale) {
+                    locale = (Locale) obj;
+                    break;
+                }
+                obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i].toUpperCase(Locale.ENGLISH));
+                if (null != obj && obj instanceof Locale) {
+                    locale = (Locale) obj;
+                    break;
+                }
+            }
+
+            if (null != locale) {
+                return locale;
+            }
+
+            // Tapestry 3.0: Engine stored in session under "org.apache.tapestry.engine:" + config.getServletName()
+            // TODO: Tapestry 4+
+            {
+                final List<Object> tapestryArray = new ArrayList<Object>();
+                for (Enumeration<String> enumeration = in_session.getAttributeNames(); enumeration.hasMoreElements();) {
+                    String name = enumeration.nextElement();
+                    if (name.indexOf("tapestry") > -1 && name.indexOf("engine") > -1 && null != in_session.getAttribute(name)) {//$NON-NLS-1$ //$NON-NLS-2$
+                        tapestryArray.add(in_session.getAttribute(name));
+                    }
+                }
+                if (tapestryArray.size() == 1) {
+                    // found a potential Engine! Let's call getLocale() on it.
+                    Object probableEngine = tapestryArray.get(0);
+                    if (null != probableEngine) {
+                        try {
+                            Method readMethod = probableEngine.getClass().getMethod("getLocale", (Class<?>[])null);//$NON-NLS-1$
+                            if (null != readMethod) {
+                                // Call the property getter and return the value
+                                Object possibleLocale = readMethod.invoke(probableEngine, (Object[]) null);
+                                if (null != possibleLocale && possibleLocale instanceof Locale) {
+                                    locale = (Locale) possibleLocale;
+                                }
+                            }
+                        } catch (Exception e) {
+                            // stay silent
+                        }
+                    }
+                }
+            }
+
+            if (null != locale) {
+                return locale;
+            }
+
+            // Last guess: iterate over all attributes, to find a Locale
+            // If there is only one, consider it to be /the/ locale
+            {
+                final List<Object> localeArray = new ArrayList<Object>();
+                for (Enumeration<String> enumeration = in_session.getAttributeNames(); enumeration.hasMoreElements();) {
+                    String name = enumeration.nextElement();
+                    Object obj = in_session.getAttribute(name);
+                    if (null != obj && obj instanceof Locale) {
+                        localeArray.add(obj);
+                    }
+                }
+                if (localeArray.size() == 1) {
+                    locale = (Locale) localeArray.get(0);
+                }
+            }
+
+            return locale;
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return null;
+        }
+    }
+
+    /**
+     * Try to get user from the session, if possible.
+     * @param in_session
+     * @return Object
+     */
+    public static Object guessUserFromSession(final Session in_session) {
+        if (null == in_session) {
+            return null;
+        }
+        if (in_session.getPrincipal() != null) {
+            return in_session.getPrincipal().getName();
+        }
+        HttpSession httpSession = in_session.getSession();
+        if (httpSession == null)
+            return null;
+        
+        try {
+            Object user = null;
+            // First search "known locations"
+            for (int i = 0; i < USER_TEST_ATTRIBUTES.length; ++i) {
+                Object obj = httpSession.getAttribute(USER_TEST_ATTRIBUTES[i]);
+                if (null != obj) {
+                    user = obj;
+                    break;
+                }
+                obj = httpSession.getAttribute(USER_TEST_ATTRIBUTES[i].toLowerCase(Locale.ENGLISH));
+                if (null != obj) {
+                    user = obj;
+                    break;
+                }
+                obj = httpSession.getAttribute(USER_TEST_ATTRIBUTES[i].toUpperCase(Locale.ENGLISH));
+                if (null != obj) {
+                    user = obj;
+                    break;
+                }
+            }
+
+            if (null != user) {
+                return user;
+            }
+
+            // Last guess: iterate over all attributes, to find a java.security.Principal or javax.security.auth.Subject
+            // If there is only one, consider it to be /the/ user
+            {
+                final List<Object> principalArray = new ArrayList<Object>();
+                for (Enumeration<String> enumeration = httpSession.getAttributeNames(); enumeration.hasMoreElements();) {
+                    String name = enumeration.nextElement();
+                    Object obj = httpSession.getAttribute(name);
+                    if (null != obj && (obj instanceof Principal || obj instanceof Subject)) {
+                        principalArray.add(obj);
+                    }
+                }
+                if (principalArray.size() == 1) {
+                    user = principalArray.get(0);
+                }
+            }
+
+            if (null != user) {
+                return user;
+            }
+
+            return user;
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return null;
+        }
+    }
+
+
+    public static long getUsedTimeForSession(Session in_session) {
+        try {
+            long diffMilliSeconds = in_session.getThisAccessedTime() - in_session.getCreationTime();
+            return diffMilliSeconds;
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return -1;
+        }
+    }
+
+    public static long getTTLForSession(Session in_session) {
+        try {
+            long diffMilliSeconds = (1000*in_session.getMaxInactiveInterval()) - (System.currentTimeMillis() - in_session.getThisAccessedTime());
+            return diffMilliSeconds;
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return -1;
+        }
+    }
+
+    public static long getInactiveTimeForSession(Session in_session) {
+        try {
+            long diffMilliSeconds =  System.currentTimeMillis() - in_session.getThisAccessedTime();
+            return diffMilliSeconds;
+        } catch (IllegalStateException ise) {
+            //ignore: invalidated session
+            return -1;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ClassNameMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ClassNameMBean.java
new file mode 100644
index 0000000..8ba82ac
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ClassNameMBean.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import javax.management.MBeanException;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+
+
+/**
+ * <p>A convenience base class for <strong>ModelMBean</strong> implementations
+ * where the underlying base class (and therefore the set of supported
+ * properties) is different for varying implementations of a standard
+ * interface.  For Catalina, that includes at least the following:
+ * Connector, Logger, Realm, and Valve.  This class creates an artificial
+ * MBean attribute named <code>className</code>, which reports the fully
+ * qualified class name of the managed object as its value.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ClassNameMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class ClassNameMBean extends BaseModelMBean {
+
+
+     // ---------------------------------------------------------- Constructors
+
+
+     /**
+      * Construct a <code>ModelMBean</code> with default
+      * <code>ModelMBeanInfo</code> information.
+      *
+      * @exception MBeanException if the initialize of an object
+      *  throws an exception
+      * @exception RuntimeOperationsException if an IllegalArgumentException
+      *  occurs
+      */
+     public ClassNameMBean()
+         throws MBeanException, RuntimeOperationsException {
+
+         super();
+
+     }
+
+
+     // ------------------------------------------------------------ Properties
+
+
+     /**
+      * Return the fully qualified Java class name of the managed object
+      * for this MBean.
+      */
+     @Override
+    public String getClassName() {
+
+         return (this.resource.getClass().getName());
+
+     }
+
+
+ }
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ConnectorMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ConnectorMBean.java
new file mode 100644
index 0000000..73900c7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ConnectorMBean.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+
+import org.apache.catalina.connector.Connector;
+import org.apache.tomcat.util.IntrospectionUtils;
+
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.coyote.tomcat5.CoyoteConnector</code> component.</p>
+ *
+ * @author Amy Roh
+ * @version $Id: ConnectorMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class ConnectorMBean extends ClassNameMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public ConnectorMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ------------------------------------------------------------- Attributes
+
+
+    /**
+     * Obtain and return the value of a specific attribute of this MBean.
+     *
+     * @param name Name of the requested attribute
+     *
+     * @exception AttributeNotFoundException if this attribute is not
+     *  supported by this MBean
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectionException if a Java reflection exception
+     *  occurs when invoking the getter
+     */
+    @Override
+    public Object getAttribute(String name) throws AttributeNotFoundException,
+            MBeanException, ReflectionException {
+
+        // Validate the input parameters
+        if (name == null)
+            throw new RuntimeOperationsException(new IllegalArgumentException(
+                    "Attribute name is null"), "Attribute name is null");
+
+        Object result = null;
+        try {
+            Connector connector = (Connector) getManagedResource();
+            result = IntrospectionUtils.getProperty(connector, name);
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+
+        return result;
+
+    }
+
+    
+    /**
+     * Set the value of a specific attribute of this MBean.
+     *
+     * @param attribute The identification of the attribute to be set
+     *  and the new value
+     *
+     * @exception AttributeNotFoundException if this attribute is not
+     *  supported by this MBean
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectionException if a Java reflection exception
+     *  occurs when invoking the getter
+     */
+     @Override
+    public void setAttribute(Attribute attribute)
+            throws AttributeNotFoundException, MBeanException,
+            ReflectionException {
+
+        // Validate the input parameters
+        if (attribute == null)
+            throw new RuntimeOperationsException(new IllegalArgumentException(
+                    "Attribute is null"), "Attribute is null");
+        String name = attribute.getName();
+        Object value = attribute.getValue();
+        if (name == null)
+            throw new RuntimeOperationsException(new IllegalArgumentException(
+                    "Attribute name is null"), "Attribute name is null");
+
+        try {
+            Connector connector = (Connector) getManagedResource();
+            IntrospectionUtils.setProperty(connector, name, String.valueOf(value));
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+  
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/Constants.java
new file mode 100644
index 0000000..ff0c963
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.mbeans;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.mbeans";
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContainerMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContainerMBean.java
new file mode 100644
index 0000000..73a880f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContainerMBean.java
@@ -0,0 +1,325 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Valve;
+import org.apache.catalina.core.ContainerBase;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.startup.ContextConfig;
+import org.apache.catalina.startup.HostConfig;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+
+public class ContainerMBean extends BaseModelMBean {
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public ContainerMBean() 
+        throws MBeanException, RuntimeOperationsException {
+        
+        super();
+    }
+    
+    /**
+     * Add a new child Container to those associated with this Container,
+     * if supported. Won't start the child yet. Has to be started with a call to
+     * Start method after necessary configurations are done.
+     * 
+     * @param type ClassName of the child to be added
+     * @param name Name of the child to be added
+     * 
+     * @exception MBeanException if the child cannot be added
+     */
+    public void addChild(String type, String name) throws MBeanException{ 
+        Container contained = null;
+        try {
+            contained = (Container)Class.forName(type).newInstance();
+            contained.setName(name);
+            
+            if(contained instanceof StandardHost){
+                HostConfig config = new HostConfig();
+                contained.addLifecycleListener(config);
+            } else if(contained instanceof StandardContext){
+                ContextConfig config = new ContextConfig();
+                contained.addLifecycleListener(config);
+            }
+
+        } catch (InstantiationException e) {
+            throw new MBeanException(e);
+        } catch (IllegalAccessException e) {
+            throw new MBeanException(e);
+        } catch (ClassNotFoundException e) {
+            throw new MBeanException(e);
+        }
+        
+        boolean oldValue= true;
+        
+        ContainerBase container = null;
+        try {
+            container = (ContainerBase)getManagedResource();
+            oldValue = container.getStartChildren();
+            container.setStartChildren(false);
+            container.addChild(contained);
+            contained.init();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        } catch (LifecycleException e){
+            throw new MBeanException(e);
+        } finally {
+            if(container != null) {
+                container.setStartChildren(oldValue);
+            }
+        }
+    }
+    
+    /**
+     * Remove an existing child Container from association with this parent
+     * Container.
+     *
+     * @param name Name of the existing child Container to be removed
+     */
+    public void removeChild(String name) throws MBeanException{
+        if(name != null){
+            try {
+                Container container = (Container)getManagedResource();
+                Container contained = container.findChild(name);
+                container.removeChild(contained);
+            } catch (InstanceNotFoundException e) {
+                throw new MBeanException(e);
+            } catch (RuntimeOperationsException e) {
+                throw new MBeanException(e);
+            } catch (InvalidTargetObjectTypeException e) {
+                throw new MBeanException(e);
+            }
+        }
+    }
+    
+    /**
+     * Adds a valve to this Container instance.
+     *
+     * @param valveType ClassName of the valve to be added
+     * 
+     * @exception MBeanException if a component cannot be removed
+     */
+    public String addValve(String valveType) throws MBeanException{ 
+        Valve valve = null;
+        try {
+            valve = (Valve)Class.forName(valveType).newInstance();
+        } catch (InstantiationException e) {
+            throw new MBeanException(e);
+        } catch (IllegalAccessException e) {
+            throw new MBeanException(e);
+        } catch (ClassNotFoundException e) {
+            throw new MBeanException(e);
+        }
+        
+        if (valve == null) {
+            return null;
+        }
+            
+        try {
+            ContainerBase container = (ContainerBase)getManagedResource();
+            container.addValve(valve);
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        return ((LifecycleMBeanBase)valve).getObjectName().toString();
+    }
+    
+    /**
+     * Remove an existing Valve.
+     *
+     * @param valveName MBean Name of the Valve to remove
+     *
+     * @exception MBeanException if a component cannot be removed
+     */
+    public void removeValve(String valveName) throws MBeanException{
+        ContainerBase container=null;
+        try {
+            container = (ContainerBase)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        ObjectName oname;
+        try {
+            oname = new ObjectName(valveName);
+        } catch (MalformedObjectNameException e) {
+            throw new MBeanException(e);
+        } catch (NullPointerException e) {
+            throw new MBeanException(e);
+        }
+        
+        if(container != null){
+            Valve[] valves = container.getPipeline().getValves();
+            for (int i = 0; i < valves.length; i++) {
+                ObjectName voname = ((ValveBase) valves[i]).getObjectName();
+                if (voname.equals(oname)) {
+                    container.getPipeline().removeValve(valves[i]);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Add a LifecycleEvent listener to this component.
+     *
+     * @param type ClassName of the listener to add
+     */
+    public void addLifeCycleListener(String type) throws MBeanException{
+        LifecycleListener listener = null;
+        try {
+            listener = (LifecycleListener)Class.forName(type).newInstance();
+        } catch (InstantiationException e) {
+            throw new MBeanException(e);
+        } catch (IllegalAccessException e) {
+            throw new MBeanException(e);
+        } catch (ClassNotFoundException e) {
+            throw new MBeanException(e);
+        }
+        
+        if(listener != null){
+            try {
+                ContainerBase container = (ContainerBase)getManagedResource();
+                container.addLifecycleListener(listener);
+            } catch (InstanceNotFoundException e) {
+                throw new MBeanException(e);
+            } catch (RuntimeOperationsException e) {
+                throw new MBeanException(e);
+            } catch (InvalidTargetObjectTypeException e) {
+                throw new MBeanException(e);
+            }
+        }
+    }
+    
+    /**
+     * Remove a LifecycleEvent listeners from this component.
+     *
+     * @param type The ClassName of the listeners to be removed. 
+     * Note that all the listeners having given ClassName will be removed. 
+     */
+    public void removeLifeCycleListeners(String type) throws MBeanException{
+        ContainerBase container=null;
+        try {
+            container = (ContainerBase)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        LifecycleListener[] listeners = container.findLifecycleListeners();
+        for(LifecycleListener listener: listeners){
+            if(listener.getClass().getName().equals(type)){
+                container.removeLifecycleListener(listener);
+            }
+        }
+    }
+
+    
+    /**
+     * List the class name of each of the lifecycle listeners added to this
+     * container.
+     */
+    public String[] findLifecycleListenerNames() throws MBeanException {
+        ContainerBase container = null;
+        List<String> result = new ArrayList<String>();
+
+        try {
+            container = (ContainerBase) getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+
+        LifecycleListener[] listeners = container.findLifecycleListeners();
+        for(LifecycleListener listener: listeners){
+            result.add(listener.getClass().getName());
+        }
+
+        return result.toArray(new String[result.size()]);
+    }
+
+    
+    /**
+     * List the class name of each of the container listeners added to this
+     * container.
+     */
+    public String[] findContainerListenerNames() throws MBeanException {
+        ContainerBase container = null;
+        List<String> result = new ArrayList<String>();
+
+        try {
+            container = (ContainerBase) getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+
+        ContainerListener[] listeners = container.findContainerListeners();
+        for(ContainerListener listener: listeners){
+            result.add(listener.getClass().getName());
+        }
+
+        return result.toArray(new String[result.size()]);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextEnvironmentMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextEnvironmentMBean.java
new file mode 100644
index 0000000..4c029da
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextEnvironmentMBean.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+
+import org.apache.catalina.deploy.ContextEnvironment;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.deploy.ContextEnvironment</code> component.</p>
+ *
+ * @author Amy Roh
+ * @version $Id: ContextEnvironmentMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class ContextEnvironmentMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public ContextEnvironmentMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------------- Attributes
+
+    
+    /**
+     * Set the value of a specific attribute of this MBean.
+     *
+     * @param attribute The identification of the attribute to be set
+     *  and the new value
+     *
+     * @exception AttributeNotFoundException if this attribute is not
+     *  supported by this MBean
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectionException if a Java reflection exception
+     *  occurs when invoking the getter
+     */
+     @Override
+    public void setAttribute(Attribute attribute)
+        throws AttributeNotFoundException, MBeanException,
+        ReflectionException {
+
+        super.setAttribute(attribute);
+        
+        ContextEnvironment ce = null;
+        try {
+            ce = (ContextEnvironment) getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+             throw new MBeanException(e);
+        }
+        
+        // cannot use side-effects.  It's removed and added back each time 
+        // there is a modification in a resource.
+        NamingResources nr = ce.getNamingResources();
+        nr.removeEnvironment(ce.getName());
+        nr.addEnvironment(ce);
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextMBean.java
new file mode 100644
index 0000000..de4b5cd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextMBean.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.ApplicationParameter;
+import org.apache.catalina.deploy.ErrorPage;
+import org.apache.catalina.deploy.FilterDef;
+import org.apache.catalina.deploy.FilterMap;
+import org.apache.catalina.deploy.SecurityConstraint;
+
+public class ContextMBean extends ContainerMBean {
+
+    public ContextMBean() throws MBeanException, RuntimeOperationsException {
+        
+        super();
+    }
+    
+     /**
+     * Return the set of application parameters for this application.
+     */
+    public String[] findApplicationParameters() throws MBeanException {
+        
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        ApplicationParameter[] params = context.findApplicationParameters();
+        String[] stringParams = new String[params.length];
+        for(int counter=0; counter < params.length; counter++){
+           stringParams[counter]=params[counter].toString();
+        }
+        
+        return stringParams;
+        
+    }
+    
+    /**
+     * Return the security constraints for this web application.
+     * If there are none, a zero-length array is returned.
+     */
+    public String[] findConstraints() throws MBeanException {
+
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        SecurityConstraint[] constraints = context.findConstraints();
+        String[] stringConstraints = new String[constraints.length];
+        for(int counter=0; counter < constraints.length; counter++){
+            stringConstraints[counter]=constraints[counter].toString();
+        }
+        
+        return stringConstraints;
+        
+    }
+    
+    /**
+     * Return the error page entry for the specified HTTP error code,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param errorCode Error code to look up
+     */
+    public String findErrorPage(int errorCode) throws MBeanException {
+
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        return context.findErrorPage(errorCode).toString();
+        
+    }
+    
+    /**
+     * Return the error page entry for the specified Java exception type,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param exceptionType Exception type to look up
+     */
+    public String findErrorPage(String exceptionType) throws MBeanException {
+
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        return context.findErrorPage(exceptionType).toString();
+        
+    }
+    
+    /**
+     * Return the set of defined error pages for all specified error codes
+     * and exception types.
+     */
+    public String[] findErrorPages() throws MBeanException {
+        
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        ErrorPage[] pages = context.findErrorPages();
+        String[] stringPages = new String[pages.length];
+        for(int counter=0; counter < pages.length; counter++){
+            stringPages[counter]=pages[counter].toString();
+        }
+        
+        return stringPages;
+        
+    }
+    
+    /**
+     * Return the filter definition for the specified filter name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param name Filter name to look up
+     */
+    public String findFilterDef(String name) throws MBeanException {
+        
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        FilterDef filterDef = context.findFilterDef(name);
+        return filterDef.toString();
+        
+    }
+    
+    /**
+     * Return the set of defined filters for this Context.
+     */
+    public String[] findFilterDefs() throws MBeanException {
+        
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        ApplicationParameter[] params = context.findApplicationParameters();
+        String[] stringParams = new String[params.length];
+        for(int counter=0; counter < params.length; counter++){
+            stringParams[counter]=params[counter].toString();
+        }
+        
+        return stringParams;
+        
+    }
+    
+    /**
+     * Return the set of filter mappings for this Context.
+     */
+    public String[] findFilterMaps() throws MBeanException {
+        
+        Context context; 
+        try {
+            context = (Context)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        FilterMap[] maps = context.findFilterMaps();
+        String[] stringMaps = new String[maps.length];
+        for(int counter=0; counter < maps.length; counter++){
+            stringMaps[counter]=maps[counter].toString();
+        }
+        
+        return stringMaps;
+        
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextResourceLinkMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextResourceLinkMBean.java
new file mode 100644
index 0000000..93acb16
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextResourceLinkMBean.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+
+import org.apache.catalina.deploy.ContextResourceLink;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.deploy.ContextResourceLink</code> component.</p>
+ *
+ * @author Amy Roh
+ * @version $Id: ContextResourceLinkMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class ContextResourceLinkMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public ContextResourceLinkMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------------- Attributes
+
+    /**
+     * Obtain and return the value of a specific attribute of this MBean.
+     *
+     * @param name Name of the requested attribute
+     *
+     * @exception AttributeNotFoundException if this attribute is not
+     *  supported by this MBean
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectionException if a Java reflection exception
+     *  occurs when invoking the getter
+     */
+    @Override
+    public Object getAttribute(String name)
+        throws AttributeNotFoundException, MBeanException,
+        ReflectionException {
+ 
+        // Validate the input parameters
+        if (name == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Attribute name is null"),
+                 "Attribute name is null");
+
+        ContextResourceLink cl = null;
+        try {
+            cl = (ContextResourceLink) getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+             throw new MBeanException(e);
+        }
+        
+        String value = null;
+        if ("global".equals(name)) {
+            return (cl.getGlobal());
+        } else if ("description".equals(name)) {
+            return (cl.getDescription());
+        } else if ("name".equals(name)) {
+            return (cl.getName());              
+        } else if ("type".equals(name)) {
+            return (cl.getType());
+        } else {
+            value = (String) cl.getProperty(name);
+            if (value == null) {
+                throw new AttributeNotFoundException
+                    ("Cannot find attribute "+name);
+            }
+        }
+        
+        return value;
+        
+    }
+    
+    /**
+     * Set the value of a specific attribute of this MBean.
+     *
+     * @param attribute The identification of the attribute to be set
+     *  and the new value
+     *
+     * @exception AttributeNotFoundException if this attribute is not
+     *  supported by this MBean
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectionException if a Java reflection exception
+     *  occurs when invoking the getter
+     */
+     @Override
+    public void setAttribute(Attribute attribute)
+        throws AttributeNotFoundException, MBeanException,
+        ReflectionException {
+       
+        // Validate the input parameters
+        if (attribute == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Attribute is null"),
+                 "Attribute is null");
+        
+        String name = attribute.getName();
+        Object value = attribute.getValue();
+        if (name == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Attribute name is null"),
+                 "Attribute name is null"); 
+         
+        ContextResourceLink crl = null;
+        try {
+            crl = (ContextResourceLink) getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+             throw new MBeanException(e);
+        }
+        
+        if ("global".equals(name)) {
+            crl.setGlobal((String)value);
+        } else if ("description".equals(name)) {
+            crl.setDescription((String)value);
+        } else if ("name".equals(name)) {
+            crl.setName((String)value);              
+        } else if ("type".equals(name)) {
+            crl.setType((String)value);
+        } else {
+            crl.setProperty(name, ""+value);
+        }
+        
+        // cannot use side-effects.  It's removed and added back each time 
+        // there is a modification in a resource.
+        NamingResources nr = crl.getNamingResources();
+        nr.removeResourceLink(crl.getName());
+        nr.addResourceLink(crl);
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextResourceMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextResourceMBean.java
new file mode 100644
index 0000000..34d00a8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ContextResourceMBean.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+
+import org.apache.catalina.deploy.ContextResource;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.deploy.ContextResource</code> component.</p>
+ *
+ * @author Amy Roh
+ * @version $Id: ContextResourceMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class ContextResourceMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public ContextResourceMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------------- Attributes
+
+
+    /**
+     * Obtain and return the value of a specific attribute of this MBean.
+     *
+     * @param name Name of the requested attribute
+     *
+     * @exception AttributeNotFoundException if this attribute is not
+     *  supported by this MBean
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectionException if a Java reflection exception
+     *  occurs when invoking the getter
+     */
+    @Override
+    public Object getAttribute(String name)
+        throws AttributeNotFoundException, MBeanException,
+        ReflectionException {
+ 
+        // Validate the input parameters
+        if (name == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Attribute name is null"),
+                 "Attribute name is null");
+
+        ContextResource cr = null;
+        try {
+            cr = (ContextResource) getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+             throw new MBeanException(e);
+        }
+        
+        String value = null;
+        if ("auth".equals(name)) {
+            return (cr.getAuth());
+        } else if ("description".equals(name)) {
+            return (cr.getDescription());
+        } else if ("name".equals(name)) {
+            return (cr.getName());              
+        } else if ("scope".equals(name)) {
+            return (cr.getScope());  
+        } else if ("type".equals(name)) {
+            return (cr.getType());
+        } else {
+            value = (String) cr.getProperty(name);
+            if (value == null) {
+                throw new AttributeNotFoundException
+                    ("Cannot find attribute "+name);
+            }
+        }
+        
+        return value;
+        
+    }
+
+    
+    /**
+     * Set the value of a specific attribute of this MBean.
+     *
+     * @param attribute The identification of the attribute to be set
+     *  and the new value
+     *
+     * @exception AttributeNotFoundException if this attribute is not
+     *  supported by this MBean
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectionException if a Java reflection exception
+     *  occurs when invoking the getter
+     */
+     @Override
+    public void setAttribute(Attribute attribute)
+        throws AttributeNotFoundException, MBeanException,
+        ReflectionException {
+
+        // Validate the input parameters
+        if (attribute == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Attribute is null"),
+                 "Attribute is null");
+        String name = attribute.getName();
+        Object value = attribute.getValue();
+        if (name == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Attribute name is null"),
+                 "Attribute name is null"); 
+        
+        ContextResource cr = null;
+        try {
+            cr = (ContextResource) getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+             throw new MBeanException(e);
+        }
+        
+        if ("auth".equals(name)) {
+            cr.setAuth((String)value);
+        } else if ("description".equals(name)) {
+            cr.setDescription((String)value);
+        } else if ("name".equals(name)) {
+            cr.setName((String)value);              
+        } else if ("scope".equals(name)) {
+            cr.setScope((String)value);  
+        } else if ("type".equals(name)) {
+            cr.setType((String)value);
+        } else {
+            cr.setProperty(name, ""+value);
+        }
+        
+        // cannot use side-effects.  It's removed and added back each time 
+        // there is a modification in a resource.
+        NamingResources nr = cr.getNamingResources();
+        nr.removeResource(cr.getName());
+        nr.addResource(cr);
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/GlobalResourcesLifecycleListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/GlobalResourcesLifecycleListener.java
new file mode 100644
index 0000000..eca1fca
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/GlobalResourcesLifecycleListener.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import java.util.Iterator;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.OperationNotSupportedException;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+import org.apache.catalina.UserDatabase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.modeler.Registry;
+
+
+/**
+ * Implementation of <code>LifecycleListener</code> that instantiates the
+ * set of MBeans associated with global JNDI resources that are subject to
+ * management.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: GlobalResourcesLifecycleListener.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class GlobalResourcesLifecycleListener
+    implements LifecycleListener {
+    private static final Log log = LogFactory.getLog(GlobalResourcesLifecycleListener.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The owning Catalina component that we are attached to.
+     */
+    protected Lifecycle component = null;
+
+
+    /**
+     * The configuration information registry for our managed beans.
+     */
+    protected static Registry registry = MBeanUtils.createRegistry();
+
+
+    // ---------------------------------------------- LifecycleListener Methods
+
+
+    /**
+     * Primary entry point for startup and shutdown events.
+     *
+     * @param event The event that has occurred
+     */
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        if (Lifecycle.START_EVENT.equals(event.getType())) {
+            component = event.getLifecycle();
+            createMBeans();
+        } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
+            destroyMBeans();
+            component = null;
+        }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Create the MBeans for the interesting global JNDI resources.
+     */
+    protected void createMBeans() {
+
+        // Look up our global naming context
+        Context context = null;
+        try {
+            context = (Context) (new InitialContext()).lookup("java:/");
+        } catch (NamingException e) {
+            log.error("No global naming context defined for server");
+            return;
+        }
+
+        // Recurse through the defined global JNDI resources context
+        try {
+            createMBeans("", context);
+        } catch (NamingException e) {
+            log.error("Exception processing Global JNDI Resources", e);
+        }
+
+    }
+
+
+    /**
+     * Create the MBeans for the interesting global JNDI resources in
+     * the specified naming context.
+     *
+     * @param prefix Prefix for complete object name paths
+     * @param context Context to be scanned
+     *
+     * @exception NamingException if a JNDI exception occurs
+     */
+    protected void createMBeans(String prefix, Context context)
+        throws NamingException {
+
+        if (log.isDebugEnabled()) {
+            log.debug("Creating MBeans for Global JNDI Resources in Context '" +
+                prefix + "'");
+        }
+
+        try {
+            NamingEnumeration<Binding> bindings = context.listBindings("");
+            while (bindings.hasMore()) {
+                Binding binding = bindings.next();
+                String name = prefix + binding.getName();
+                Object value = context.lookup(binding.getName());
+                if (log.isDebugEnabled()) {
+                    log.debug("Checking resource " + name);
+                }
+                if (value instanceof Context) {
+                    createMBeans(name + "/", (Context) value);
+                } else if (value instanceof UserDatabase) {
+                    try {
+                        createMBeans(name, (UserDatabase) value);
+                    } catch (Exception e) {
+                        log.error("Exception creating UserDatabase MBeans for " + name,
+                                e);
+                    }
+                }
+            }
+        } catch( RuntimeException ex) {
+            log.error("RuntimeException " + ex);
+        } catch( OperationNotSupportedException ex) {
+            log.error("Operation not supported " + ex);
+        }
+
+    }
+
+
+    /**
+     * Create the MBeans for the specified UserDatabase and its contents.
+     *
+     * @param name Complete resource name of this UserDatabase
+     * @param database The UserDatabase to be processed
+     *
+     * @exception Exception if an exception occurs while creating MBeans
+     */
+    protected void createMBeans(String name, UserDatabase database)
+        throws Exception {
+
+        // Create the MBean for the UserDatabase itself
+        if (log.isDebugEnabled()) {
+            log.debug("Creating UserDatabase MBeans for resource " + name);
+            log.debug("Database=" + database);
+        }
+        if (MBeanUtils.createMBean(database) == null) {
+            throw new IllegalArgumentException
+                ("Cannot create UserDatabase MBean for resource " + name);
+        }
+
+        // Create the MBeans for each defined Role
+        Iterator<Role> roles = database.getRoles();
+        while (roles.hasNext()) {
+            Role role = roles.next();
+            if (log.isDebugEnabled()) {
+                log.debug("  Creating Role MBean for role " + role);
+            }
+            if (MBeanUtils.createMBean(role) == null) {
+                throw new IllegalArgumentException
+                    ("Cannot create Role MBean for role " + role);
+            }
+        }
+
+        // Create the MBeans for each defined Group
+        Iterator<Group> groups = database.getGroups();
+        while (groups.hasNext()) {
+            Group group = groups.next();
+            if (log.isDebugEnabled()) {
+                log.debug("  Creating Group MBean for group " + group);
+            }
+            if (MBeanUtils.createMBean(group) == null) {
+                throw new IllegalArgumentException
+                    ("Cannot create Group MBean for group " + group);
+            }
+        }
+
+        // Create the MBeans for each defined User
+        Iterator<User> users = database.getUsers();
+        while (users.hasNext()) {
+            User user = users.next();
+            if (log.isDebugEnabled()) {
+                log.debug("  Creating User MBean for user " + user);
+            }
+            if (MBeanUtils.createMBean(user) == null) {
+                throw new IllegalArgumentException
+                    ("Cannot create User MBean for user " + user);
+            }
+        }
+
+    }
+
+
+    /**
+     * Destroy the MBeans for the interesting global JNDI resources.
+     */
+    protected void destroyMBeans() {
+
+        if (log.isDebugEnabled()) {
+            log.debug("Destroying MBeans for Global JNDI Resources");
+        }
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/GroupMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/GroupMBean.java
new file mode 100644
index 0000000..449317a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/GroupMBean.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.management.MBeanException;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.Group</code> component.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: GroupMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class GroupMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public GroupMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The configuration information registry for our managed beans.
+     */
+    protected Registry registry = MBeanUtils.createRegistry();
+
+
+    /**
+     * The <code>ManagedBean</code> information describing this MBean.
+     */
+    protected ManagedBean managed =
+        registry.findManagedBean("Group");
+
+
+    // ------------------------------------------------------------- Attributes
+
+
+    /**
+     * Return the MBean Names of all authorized roles for this group.
+     */
+    public String[] getRoles() {
+
+        Group group = (Group) this.resource;
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<Role> roles = group.getRoles();
+        while (roles.hasNext()) {
+            Role role = null;
+            try {
+                role = roles.next();
+                ObjectName oname =
+                    MBeanUtils.createObjectName(managed.getDomain(), role);
+                results.add(oname.toString());
+            } catch (MalformedObjectNameException e) {
+                IllegalArgumentException iae = new IllegalArgumentException
+                    ("Cannot create object name for role " + role);
+                iae.initCause(e);
+                throw iae;
+            }
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+
+    /**
+     * Return the MBean Names of all users that are members of this group.
+     */
+    public String[] getUsers() {
+
+        Group group = (Group) this.resource;
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<User> users = group.getUsers();
+        while (users.hasNext()) {
+            User user = null;
+            try {
+                user = users.next();
+                ObjectName oname =
+                    MBeanUtils.createObjectName(managed.getDomain(), user);
+                results.add(oname.toString());
+            } catch (MalformedObjectNameException e) {
+                IllegalArgumentException iae = new IllegalArgumentException
+                    ("Cannot create object name for user " + user);
+                iae.initCause(e);
+                throw iae;
+            }
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+
+    // ------------------------------------------------------------- Operations
+
+
+    /**
+     * Add a new {@link Role} to those this group belongs to.
+     *
+     * @param rolename Role name of the new role
+     */
+    public void addRole(String rolename) {
+
+        Group group = (Group) this.resource;
+        if (group == null) {
+            return;
+        }
+        Role role = group.getUserDatabase().findRole(rolename);
+        if (role == null) {
+            throw new IllegalArgumentException
+                ("Invalid role name '" + rolename + "'");
+        }
+        group.addRole(role);
+
+    }
+
+
+    /**
+     * Remove a {@link Role} from those this group belongs to.
+     *
+     * @param rolename Role name of the old role
+     */
+    public void removeRole(String rolename) {
+
+        Group group = (Group) this.resource;
+        if (group == null) {
+            return;
+        }
+        Role role = group.getUserDatabase().findRole(rolename);
+        if (role == null) {
+            throw new IllegalArgumentException
+                ("Invalid role name '" + rolename + "'");
+        }
+        group.removeRole(role);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
new file mode 100644
index 0000000..b8226df
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.management.ManagementFactory;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.util.HashMap;
+
+import javax.management.MBeanServer;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+import javax.rmi.ssl.SslRMIServerSocketFactory;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * This listener fixes the port used by JMX/RMI Server making things much
+ * simpler if you need to connect jconsole or similar to a remote Tomcat
+ * instance that is running behind a firewall. Only the ports are configured via
+ * the listener. The remainder of the configuration is via the standard system
+ * properties for configuring JMX.
+ */
+public class JmxRemoteLifecycleListener implements LifecycleListener {
+    
+    private static final Log log =
+        LogFactory.getLog(JmxRemoteLifecycleListener.class);
+    
+    /**
+     * The string resources for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    protected int rmiRegistryPortPlatform = -1;
+    protected int rmiServerPortPlatform = -1;
+    protected boolean rmiSSL = true;
+    protected String ciphers[] = null;
+    protected String protocols[] = null;
+    protected boolean clientAuth = true;
+    protected boolean authenticate = true;
+    protected String passwordFile = null;
+    protected String accessFile = null;
+    protected boolean useLocalPorts = false;
+
+    protected JMXConnectorServer csPlatform = null;
+    
+    /**
+     * Get the port on which the Platform RMI server is exported. This is the
+     * port that is normally chosen by the RMI stack.
+     * @return The port number
+     */
+    public int getRmiServerPortPlatform() {
+        return rmiServerPortPlatform;
+    }
+    
+    /**
+     * Set the port on which the Platform RMI server is exported. This is the
+     * port that is normally chosen by the RMI stack.
+     * @param theRmiServerPortPlatform The port number
+     */
+    public void setRmiServerPortPlatform(int theRmiServerPortPlatform) {
+        rmiServerPortPlatform = theRmiServerPortPlatform;
+    }
+    
+    /**
+     * Get the port on which the Platform RMI registry is exported.
+     * @return The port number
+     */
+    public int getRmiRegistryPortPlatform() {
+        return rmiRegistryPortPlatform;
+    }
+    
+    /**
+     * Set the port on which the Platform RMI registry is exported.
+     * @param theRmiRegistryPortPlatform The port number
+     */
+    public void setRmiRegistryPortPlatform(int theRmiRegistryPortPlatform) {
+        rmiRegistryPortPlatform = theRmiRegistryPortPlatform;
+    }
+    
+    /**
+     * Get the flag that indicates that local ports should be used for all
+     * connections. If using SSH tunnels, or similar, this should be set to
+     * true to ensure the RMI client uses the tunnel.
+     * @return <code>true</code> if local ports should be used
+     */
+    public boolean getUseLocalPorts() {
+        return useLocalPorts;
+    }
+    
+    /**
+     * Set the flag that indicates that local ports should be used for all
+     * connections. If using SSH tunnels, or similar, this should be set to
+     * true to ensure the RMI client uses the tunnel.
+     * @param useLocalPorts Set to <code>true</code> if local ports should be
+     *                      used
+     */
+    public void setUseLocalPorts(boolean useLocalPorts) {
+        this.useLocalPorts = useLocalPorts;
+    }
+    
+    private void init() {
+        // Get all the other parameters required from the standard system
+        // properties. Only need to get the parameters that affect the creation
+        // of the server port.
+        String rmiSSLValue = System.getProperty(
+                "com.sun.management.jmxremote.ssl", "true");
+        rmiSSL = Boolean.parseBoolean(rmiSSLValue);
+
+        String protocolsValue = System.getProperty(
+                "com.sun.management.jmxremote.ssl.enabled.protocols");
+        if (protocolsValue != null) {
+            protocols = protocolsValue.split(",");
+        }
+
+        String ciphersValue = System.getProperty(
+                "com.sun.management.jmxremote.ssl.enabled.cipher.suites");
+        if (ciphersValue != null) {
+            ciphers = ciphersValue.split(",");
+        }
+
+        String clientAuthValue = System.getProperty(
+            "com.sun.management.jmxremote.ssl.need.client.auth", "true");
+        clientAuth = Boolean.parseBoolean(clientAuthValue);
+
+        String authenticateValue = System.getProperty(
+                "com.sun.management.jmxremote.authenticate", "true");
+        authenticate = Boolean.parseBoolean(authenticateValue);
+
+        passwordFile = System.getProperty(
+                "com.sun.management.jmxremote.password.file",
+                "jmxremote.password");
+
+        accessFile = System.getProperty(
+                "com.sun.management.jmxremote.access.file",
+                "jmxremote.access");
+    }
+    
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        // When the server starts, configure JMX/RMI
+        if (Lifecycle.START_EVENT == event.getType()) {
+            // Configure using standard jmx system properties 
+            init();
+
+            // Prevent an attacker guessing the RMI object ID
+            System.setProperty("java.rmi.server.randomIDs", "true");
+
+            // Create the environment
+            HashMap<String,Object> env = new HashMap<String,Object>();
+
+            RMIClientSocketFactory csf = null;
+            RMIServerSocketFactory ssf = null;
+
+            // Configure SSL for RMI connection if required
+            if (rmiSSL) {
+                csf = new SslRMIClientSocketFactory();
+                ssf = new SslRMIServerSocketFactory(ciphers, protocols,
+                            clientAuth);
+            }
+            
+            // Force the use of local ports if required
+            if (useLocalPorts) {
+                csf = new RmiClientLocalhostSocketFactory(csf);
+            }
+
+            // Populate the env properties used to create the server
+            if (csf != null) {
+                env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE,
+                        csf);
+            }
+            if (ssf != null) {
+                env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
+                        ssf);
+            }
+
+            // Configure authentication
+            if (authenticate) {
+                env.put("jmx.remote.x.password.file", passwordFile);
+                env.put("jmx.remote.x.access.file", accessFile);
+            }
+
+
+            // Create the Platform server
+            csPlatform = createServer("Platform", rmiRegistryPortPlatform,
+                    rmiServerPortPlatform, env,
+                    ManagementFactory.getPlatformMBeanServer());
+            
+        } else if (Lifecycle.STOP_EVENT == event.getType()) {
+            destroyServer("Platform", csPlatform);
+        }
+    }
+
+    private JMXConnectorServer createServer(String serverName,
+            int theRmiRegistryPort, int theRmiServerPort,
+            HashMap<String,Object> theEnv, MBeanServer theMBeanServer) {
+        
+        // Create the RMI registry
+        try {
+            LocateRegistry.createRegistry(theRmiRegistryPort);
+        } catch (RemoteException e) {
+            log.error(sm.getString(
+                    "jmxRemoteLifecycleListener.createRegistryFailed",
+                    serverName, Integer.toString(theRmiRegistryPort)), e);
+            return null;
+        }
+
+        // Build the connection string with fixed ports
+        StringBuilder url = new StringBuilder();
+        url.append("service:jmx:rmi://localhost:");
+        url.append(theRmiServerPort);
+        url.append("/jndi/rmi://localhost:");
+        url.append(theRmiRegistryPort);
+        url.append("/jmxrmi");
+        JMXServiceURL serviceUrl;
+        try {
+            serviceUrl = new JMXServiceURL(url.toString());
+        } catch (MalformedURLException e) {
+            log.error(sm.getString(
+                    "jmxRemoteLifecycleListener.invalidURL",
+                    serverName, url.toString()), e);
+            return null;
+        }
+        
+        // Start the JMX server with the connection string
+        JMXConnectorServer cs = null;
+        try {
+            cs = JMXConnectorServerFactory.newJMXConnectorServer(
+                    serviceUrl, theEnv, theMBeanServer);
+            cs.start();
+            log.info(sm.getString("jmxRemoteLifecycleListener.start",
+                    Integer.toString(theRmiRegistryPort),
+                    Integer.toString(theRmiServerPort), serverName));
+        } catch (IOException e) {
+            log.error(sm.getString(
+                    "jmxRemoteLifecycleListener.createServerFailed",
+                    serverName), e);
+        }
+        return cs;
+    }
+
+    private void destroyServer(String serverName,
+            JMXConnectorServer theConnectorServer) {
+        if (theConnectorServer != null) {
+            try {
+                theConnectorServer.stop();
+            } catch (IOException e) {
+                log.error(sm.getString(
+                        "jmxRemoteLifecycleListener.destroyServerFailed",
+                        serverName),e);
+            }
+        }
+    }
+
+    public static class RmiClientLocalhostSocketFactory
+    implements RMIClientSocketFactory, Serializable {
+        private static final long serialVersionUID = 1L;
+
+        private static final String FORCED_HOST = "localhost";
+
+        private RMIClientSocketFactory factory = null;
+        
+        public RmiClientLocalhostSocketFactory(RMIClientSocketFactory theFactory) {
+            factory = theFactory;
+        }
+
+        @Override
+        public Socket createSocket(String host, int port) throws IOException {
+            if (factory == null) {
+                return new Socket(FORCED_HOST, port);
+            } else {
+                return factory.createSocket(FORCED_HOST, port);
+            }
+        }
+
+        
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/LocalStrings.properties
new file mode 100644
index 0000000..26ed927
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/LocalStrings.properties
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+jmxRemoteLifecycleListener.createRegistryFailed=Unable to create the RMI registry for the {0} server using port {1}
+jmxRemoteLifecycleListener.createServerFailed=The JMX connector server could not be created or failed to start for the {0} server
+jmxRemoteLifecycleListener.destroyServerFailed=The JMX connector server could not be stopped for the {0} server
+jmxRemoteLifecycleListener.invalidURL=The JMX Service URL requested for the {0} server, "{1}", was invalid
+jmxRemoteLifecycleListener.start=The JMX Remote Listener has configured the registry on port {0} and the server on port {1} for the {2} server
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/LocalStrings_fr.properties
new file mode 100644
index 0000000..e5b8bb1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/LocalStrings_fr.properties
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+jmxRemoteLifecycleListener.createRegistryFailed=Cr\u00e9ation du r\u00e9pertoire RMI impossible pour le serveur {0} utilisant le port {1}
+jmxRemoteLifecycleListener.createServerFailed=Le connecteur serveur JMX pour le serveur {0} n''a pas pu \u00eatre cr\u00e9\u00e9 n''a pas d\u00e9marr\u00e9
+jmxRemoteLifecycleListener.destroyServerFailed=Le connecteur serveur JMX pour le serveur {0} n''a pas pu \u00eatre stopp\u00e9
+jmxRemoteLifecycleListener.invalidURL=L''URL demand\u00e9e pour le serveur {0}, "{1}", est incorrect
+jmxRemoteLifecycleListener.start=L''\u00e9couteur distant JMX a configur\u00e9 le r\u00e9pertoire sur le port {0} et le serveur sur le port {1} pour le serveur {2}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MBeanFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MBeanFactory.java
new file mode 100644
index 0000000..f83fa86
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MBeanFactory.java
@@ -0,0 +1,993 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+import java.io.File;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.Valve;
+import org.apache.catalina.authenticator.SingleSignOn;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.core.ContainerBase;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardEngine;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.core.StandardService;
+import org.apache.catalina.loader.WebappLoader;
+import org.apache.catalina.realm.DataSourceRealm;
+import org.apache.catalina.realm.JDBCRealm;
+import org.apache.catalina.realm.JNDIRealm;
+import org.apache.catalina.realm.MemoryRealm;
+import org.apache.catalina.realm.UserDatabaseRealm;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.startup.ContextConfig;
+import org.apache.catalina.startup.HostConfig;
+import org.apache.catalina.valves.AccessLogValve;
+import org.apache.catalina.valves.RemoteAddrValve;
+import org.apache.catalina.valves.RemoteHostValve;
+import org.apache.catalina.valves.ValveBase;
+
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.core.StandardServer</code> component.</p>
+ *
+ * @author Amy Roh
+ * @version $Id: MBeanFactory.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class MBeanFactory {
+
+    private static final org.apache.juli.logging.Log log = 
+        org.apache.juli.logging.LogFactory.getLog(MBeanFactory.class);
+
+    /**
+     * The <code>MBeanServer</code> for this application.
+     */
+    private static MBeanServer mserver = MBeanUtils.createServer();
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception javax.management.MBeanException if the initializer of an
+     *  object throws an exception
+     * @exception javax.management.RuntimeOperationsException if an
+     *  IllegalArgumentException occurs
+     */
+    public MBeanFactory() {
+
+        super();
+        
+    }
+
+
+    // ------------------------------------------------------------- Attributes
+
+    /**
+     * The container (Server/Service) for which this factory was created.
+     */
+    private Object container;
+
+
+    // ------------------------------------------------------------- Operations
+
+    /**
+     * Set the container that this factory was created for.
+     */
+    public void setContainer(Object container) {
+        this.container = container;
+    }
+
+    /**
+     * Return the managed bean definition for the specified bean type
+     *
+     * @param type MBean type
+     */
+    public String findObjectName(String type) {
+
+        if (type.equals("org.apache.catalina.core.StandardContext")) {
+            return "StandardContext";
+        } else if (type.equals("org.apache.catalina.core.StandardEngine")) {
+            return "Engine";
+        } else if (type.equals("org.apache.catalina.core.StandardHost")) {
+            return "Host";
+        } else {
+            return null;
+        }
+
+    }
+
+
+    /**
+     * Little convenience method to remove redundant code
+     * when retrieving the path string
+     *
+     * @param t path string
+     * @return empty string if t==null || t.equals("/")
+     */
+    private final String getPathStr(String t) {
+        if (t == null || t.equals("/")) {
+            return "";
+        }
+        return t;
+    }
+    
+   /**
+     * Get Parent ContainerBase to add its child component 
+     * from parent's ObjectName
+     */
+    private ContainerBase getParentContainerFromParent(ObjectName pname) 
+        throws Exception {
+        
+        String type = pname.getKeyProperty("type");
+        String j2eeType = pname.getKeyProperty("j2eeType");
+        Service service = getService(pname);
+        StandardEngine engine = (StandardEngine) service.getContainer();
+        if ((j2eeType!=null) && (j2eeType.equals("WebModule"))) {
+            String name = pname.getKeyProperty("name");
+            name = name.substring(2);
+            int i = name.indexOf("/");
+            String hostName = name.substring(0,i);
+            String path = name.substring(i);
+            Host host = (Host) engine.findChild(hostName);
+            String pathStr = getPathStr(path);
+            StandardContext context = (StandardContext)host.findChild(pathStr);
+            return context;
+        } else if (type != null) {
+            if (type.equals("Engine")) {
+                return engine;
+            } else if (type.equals("Host")) {
+                String hostName = pname.getKeyProperty("host");
+                StandardHost host = (StandardHost) engine.findChild(hostName);
+                return host;
+            }
+        }
+        return null;
+        
+    }
+
+
+    /**
+     * Get Parent ContainerBase to add its child component 
+     * from child component's ObjectName  as a String
+     */    
+    private ContainerBase getParentContainerFromChild(ObjectName oname) 
+        throws Exception {
+        
+        String hostName = oname.getKeyProperty("host");
+        String path = oname.getKeyProperty("path");
+        Service service = getService(oname);
+        StandardEngine engine = (StandardEngine) service.getContainer();
+        if (hostName == null) {             
+            // child's container is Engine
+            return engine;
+        } else if (path == null) {      
+            // child's container is Host
+            StandardHost host = (StandardHost) engine.findChild(hostName);
+            return host;
+        } else {                
+            // child's container is Context
+            StandardHost host = (StandardHost) engine.findChild(hostName);
+            path = getPathStr(path);
+            StandardContext context = (StandardContext) host.findChild(path);
+            return context;
+        }
+    }
+
+    
+    private Service getService(ObjectName oname) throws Exception {
+    
+        if (container instanceof Service) {
+            // Don't bother checking the domain - this is the only option
+            return (Service) container;
+        }
+
+        StandardService service = null;
+        String domain = oname.getDomain();
+        if (container instanceof Server) {
+            Service[] services = ((Server)container).findServices();
+            for (int i = 0; i < services.length; i++) {
+                service = (StandardService) services[i];
+                if (domain.equals(service.getObjectName().getDomain())) {
+                    break;
+                }
+            }
+        }
+        if (service == null ||
+                !service.getObjectName().getDomain().equals(domain)) {
+            throw new Exception("Service with the domain is not found");
+        }        
+        return service;
+
+    }
+    
+    
+    /**
+     * Create a new AccessLoggerValve.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createAccessLoggerValve(String parent)
+        throws Exception {
+
+        ObjectName pname = new ObjectName(parent);
+        // Create a new AccessLogValve instance
+        AccessLogValve accessLogger = new AccessLogValve();
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        // Add the new instance to its parent component
+        containerBase.getPipeline().addValve(accessLogger);
+        ObjectName oname = accessLogger.getObjectName();
+        return (oname.toString());
+
+    }
+        
+
+    /**
+     * Create a new AjpConnector
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param address The IP address on which to bind
+     * @param port TCP port number to listen on
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createAjpConnector(String parent, String address, int port)
+        throws Exception {
+
+        return createConnector(parent, address, port, true, false);
+    }
+    
+    /**
+     * Create a new DataSource Realm.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createDataSourceRealm(String parent, String dataSourceName, 
+        String roleNameCol, String userCredCol, String userNameCol, 
+        String userRoleTable, String userTable) throws Exception {
+
+        // Create a new DataSourceRealm instance
+        DataSourceRealm realm = new DataSourceRealm();
+        realm.setDataSourceName(dataSourceName);
+        realm.setRoleNameCol(roleNameCol);
+        realm.setUserCredCol(userCredCol);
+        realm.setUserNameCol(userNameCol);
+        realm.setUserRoleTable(userRoleTable);
+        realm.setUserTable(userTable);
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        // Add the new instance to its parent component
+        containerBase.setRealm(realm);
+        // Return the corresponding MBean name
+        ObjectName oname = realm.getObjectName();
+        if (oname != null) {
+            return (oname.toString());
+        } else {
+            return null;
+        }   
+
+    }
+
+    /**
+     * Create a new HttpConnector
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param address The IP address on which to bind
+     * @param port TCP port number to listen on
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createHttpConnector(String parent, String address, int port)
+            throws Exception {
+        return createConnector(parent, address, port, false, false);
+    }
+
+    /**
+     * Create a new Connector
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param address The IP address on which to bind
+     * @param port TCP port number to listen on
+     * @param isAjp Create a AJP/1.3 Connector
+     * @param isSSL Create a secure Connector
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    private String createConnector(String parent, String address, int port, boolean isAjp, boolean isSSL)
+        throws Exception {
+        Connector retobj = new Connector();
+        if ((address!=null) && (address.length()>0)) {
+            retobj.setProperty("address", address);
+        }
+        // Set port number
+        retobj.setPort(port);
+        // Set the protocol
+        retobj.setProtocol(isAjp ? "AJP/1.3" : "HTTP/1.1");
+        // Set SSL
+        retobj.setSecure(isSSL);
+        retobj.setScheme(isSSL ? "https" : "http");
+        // Add the new instance to its parent component
+        // FIX ME - addConnector will fail
+        ObjectName pname = new ObjectName(parent);
+        Service service = getService(pname);
+        service.addConnector(retobj);
+        
+        // Return the corresponding MBean name
+        ObjectName coname = retobj.getObjectName();
+        
+        return (coname.toString());
+    }
+
+
+    /**
+     * Create a new HttpsConnector
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param address The IP address on which to bind
+     * @param port TCP port number to listen on
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createHttpsConnector(String parent, String address, int port)
+        throws Exception {
+        return createConnector(parent, address, port, false, true);
+    }
+
+    /**
+     * Create a new JDBC Realm.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createJDBCRealm(String parent, String driverName, 
+        String connectionName, String connectionPassword, String connectionURL)
+        throws Exception {
+
+        // Create a new JDBCRealm instance
+        JDBCRealm realm = new JDBCRealm();
+        realm.setDriverName(driverName);
+        realm.setConnectionName(connectionName);
+        realm.setConnectionPassword(connectionPassword);
+        realm.setConnectionURL(connectionURL);
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        // Add the new instance to its parent component
+        containerBase.setRealm(realm);
+        // Return the corresponding MBean name
+        ObjectName oname = realm.getObjectName();
+
+        if (oname != null) {
+            return (oname.toString());
+        } else {
+            return null;
+        }   
+
+    }
+
+
+    /**
+     * Create a new JNDI Realm.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createJNDIRealm(String parent)
+        throws Exception {
+
+         // Create a new JNDIRealm instance
+        JNDIRealm realm = new JNDIRealm();
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        // Add the new instance to its parent component
+        containerBase.setRealm(realm);
+        // Return the corresponding MBean name
+        ObjectName oname = realm.getObjectName();
+
+        if (oname != null) {
+            return (oname.toString());
+        } else {
+            return null;
+        }   
+
+
+    }
+
+
+    /**
+     * Create a new Memory Realm.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createMemoryRealm(String parent)
+        throws Exception {
+
+         // Create a new MemoryRealm instance
+        MemoryRealm realm = new MemoryRealm();
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        // Add the new instance to its parent component
+        containerBase.setRealm(realm);
+        // Return the corresponding MBean name
+        ObjectName oname = realm.getObjectName();
+        if (oname != null) {
+            return (oname.toString());
+        } else {
+            return null;
+        }   
+
+    }
+
+
+    /**
+     * Create a new Remote Address Filter Valve.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createRemoteAddrValve(String parent)
+        throws Exception {
+
+        // Create a new RemoteAddrValve instance
+        RemoteAddrValve valve = new RemoteAddrValve();
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        containerBase.getPipeline().addValve(valve);
+        ObjectName oname = valve.getObjectName();
+        return (oname.toString());
+
+    }
+
+
+     /**
+     * Create a new Remote Host Filter Valve.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createRemoteHostValve(String parent)
+        throws Exception {
+
+        // Create a new RemoteHostValve instance
+        RemoteHostValve valve = new RemoteHostValve();
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        containerBase.getPipeline().addValve(valve);
+        ObjectName oname = valve.getObjectName();
+        return (oname.toString());
+        
+    }
+
+
+    /**
+     * Create a new Single Sign On Valve.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createSingleSignOn(String parent)
+        throws Exception {
+
+        // Create a new SingleSignOn instance
+        SingleSignOn valve = new SingleSignOn();
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        containerBase.getPipeline().addValve(valve);
+        ObjectName oname = valve.getObjectName();
+        return (oname.toString());
+
+    }
+    
+    
+   /**
+     * Create a new StandardContext.
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param path The context path for this Context
+     * @param docBase Document base directory (or WAR) for this Context
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createStandardContext(String parent, 
+                                        String path,
+                                        String docBase)
+        throws Exception {
+                                            
+        return createStandardContext(parent, path, docBase, false, false,
+                false, false);                                  
+    }
+
+
+   /**
+     * Create a new StandardContext.
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param path The context path for this Context
+     * @param docBase Document base directory (or WAR) for this Context
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createStandardContext(String parent, 
+                                        String path,
+                                        String docBase,
+                                        boolean xmlValidation,
+                                        boolean xmlNamespaceAware,
+                                        boolean tldValidation,
+                                        boolean tldNamespaceAware)
+        throws Exception {
+
+        // Create a new StandardContext instance
+        StandardContext context = new StandardContext();
+        path = getPathStr(path);
+        context.setPath(path);
+        context.setDocBase(docBase);
+        context.setXmlValidation(xmlValidation);
+        context.setXmlNamespaceAware(xmlNamespaceAware);
+        context.setTldValidation(tldValidation);
+        context.setTldNamespaceAware(tldNamespaceAware);
+        
+        ContextConfig contextConfig = new ContextConfig();
+        context.addLifecycleListener(contextConfig);
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ObjectName deployer = new ObjectName(pname.getDomain()+
+                                             ":type=Deployer,host="+
+                                             pname.getKeyProperty("host"));
+        if(mserver.isRegistered(deployer)) {
+            String contextName = context.getName();
+            mserver.invoke(deployer, "addServiced",
+                           new Object [] {contextName},
+                           new String [] {"java.lang.String"});
+            String configPath = (String)mserver.getAttribute(deployer,
+                                                             "configBaseName");
+            String baseName = context.getBaseName();
+            File configFile = new File(new File(configPath), baseName+".xml");
+            context.setConfigFile(configFile.toURI().toURL());
+            mserver.invoke(deployer, "manageApp",
+                           new Object[] {context},
+                           new String[] {"org.apache.catalina.Context"});
+            mserver.invoke(deployer, "removeServiced",
+                           new Object [] {contextName},
+                           new String [] {"java.lang.String"});
+        } else {
+            log.warn("Deployer not found for "+pname.getKeyProperty("host"));
+            Service service = getService(pname);
+            Engine engine = (Engine) service.getContainer();
+            Host host = (Host) engine.findChild(pname.getKeyProperty("host"));
+            host.addChild(context);
+        }
+
+        // Return the corresponding MBean name
+        return context.getObjectName().toString();
+
+    }
+
+
+    /**
+     * Create a new StandardHost.
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param name Unique name of this Host
+     * @param appBase Application base directory name
+     * @param autoDeploy Should we auto deploy?
+     * @param deployOnStartup Deploy on server startup?
+     * @param deployXML Should we deploy Context XML config files property?
+     * @param unpackWARs Should we unpack WARs when auto deploying?
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createStandardHost(String parent, String name,
+                                     String appBase,
+                                     boolean autoDeploy,
+                                     boolean deployOnStartup,
+                                     boolean deployXML,                                       
+                                     boolean unpackWARs)
+        throws Exception {
+
+        // Create a new StandardHost instance
+        StandardHost host = new StandardHost();
+        host.setName(name);
+        host.setAppBase(appBase);
+        host.setAutoDeploy(autoDeploy);
+        host.setDeployOnStartup(deployOnStartup);
+        host.setDeployXML(deployXML);
+        host.setUnpackWARs(unpackWARs);
+    
+        // add HostConfig for active reloading
+        HostConfig hostConfig = new HostConfig();
+        host.addLifecycleListener(hostConfig);
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        Service service = getService(pname);
+        Engine engine = (Engine) service.getContainer();
+        engine.addChild(host);
+
+        // Return the corresponding MBean name
+        return (host.getObjectName().toString());
+
+    }
+
+
+    /**
+     * Creates a new StandardService and StandardEngine.
+     *
+     * @param domain       Domain name for the container instance
+     * @param defaultHost  Name of the default host to be used in the Engine
+     * @param baseDir      Base directory value for Engine 
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createStandardServiceEngine(String domain,
+            String defaultHost, String baseDir) throws Exception{
+
+        if (!(container instanceof Server)) {
+            throw new Exception("Container not Server");
+        }
+        
+        StandardEngine engine = new StandardEngine();
+        engine.setDomain(domain);
+        engine.setName(domain);
+        engine.setDefaultHost(defaultHost);
+        engine.setBaseDir(baseDir);
+
+        Service service = new StandardService();
+        service.setContainer(engine);
+        service.setName(domain);
+        
+        ((Server) container).addService(service);
+        
+        return engine.getObjectName().toString();
+    }
+    
+    
+    /**
+     * Create a new StandardManager.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createStandardManager(String parent)
+        throws Exception {
+
+        // Create a new StandardManager instance
+        StandardManager manager = new StandardManager();
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        if (containerBase != null) {
+            containerBase.setManager(manager);
+        } 
+        ObjectName oname = manager.getObjectName();
+        if (oname != null) {
+            return (oname.toString());
+        } else {
+            return null;
+        }
+        
+    }
+
+
+    /**
+     * Create a new  UserDatabaseRealm.
+     *
+     * @param parent MBean Name of the associated parent component
+     * @param resourceName Global JNDI resource name of the associated
+     *  UserDatabase
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createUserDatabaseRealm(String parent, String resourceName)
+        throws Exception {
+
+         // Create a new UserDatabaseRealm instance
+        UserDatabaseRealm realm = new UserDatabaseRealm();
+        realm.setResourceName(resourceName);
+        
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        // Add the new instance to its parent component
+        containerBase.setRealm(realm);
+        // Return the corresponding MBean name
+        ObjectName oname = realm.getObjectName();
+        // FIXME getObjectName() returns null
+        //ObjectName oname = 
+        //    MBeanUtils.createObjectName(pname.getDomain(), realm);
+        if (oname != null) {
+            return (oname.toString());
+        } else {
+            return null;
+        }   
+
+    }
+
+
+    /**
+     * Create a new Web Application Loader.
+     *
+     * @param parent MBean Name of the associated parent component
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public String createWebappLoader(String parent)
+        throws Exception {
+
+        // Create a new WebappLoader instance
+        WebappLoader loader = new WebappLoader();
+
+        // Add the new instance to its parent component
+        ObjectName pname = new ObjectName(parent);
+        ContainerBase containerBase = getParentContainerFromParent(pname);
+        if (containerBase != null) {
+            containerBase.setLoader(loader);
+        } 
+        // FIXME add Loader.getObjectName
+        //ObjectName oname = loader.getObjectName();
+        ObjectName oname = 
+            MBeanUtils.createObjectName(pname.getDomain(), loader);
+        return (oname.toString());
+        
+    }
+
+
+    /**
+     * Remove an existing Connector.
+     *
+     * @param name MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeConnector(String name) throws Exception {
+
+        // Acquire a reference to the component to be removed
+        ObjectName oname = new ObjectName(name);
+        Service service = getService(oname);
+        String port = oname.getKeyProperty("port");
+        //String address = oname.getKeyProperty("address");
+
+        Connector conns[] = service.findConnectors();
+
+        for (int i = 0; i < conns.length; i++) {
+            String connAddress = String.valueOf(conns[i].getProperty("address"));
+            String connPort = ""+conns[i].getPort();
+
+            // if (((address.equals("null")) &&
+            if ((connAddress==null) && port.equals(connPort)) {
+                service.removeConnector(conns[i]);
+                conns[i].destroy();
+                break;
+            }
+            // } else if (address.equals(connAddress))
+            if (port.equals(connPort)) {
+                // Remove this component from its parent component
+                service.removeConnector(conns[i]);
+                conns[i].destroy();
+                break;
+            }
+        }
+
+    }
+
+
+    /**
+     * Remove an existing Context.
+     *
+     * @param contextName MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeContext(String contextName) throws Exception {
+
+        // Acquire a reference to the component to be removed
+        ObjectName oname = new ObjectName(contextName);
+        String domain = oname.getDomain();
+        StandardService service = (StandardService) getService(oname);
+
+        Engine engine = (Engine) service.getContainer();
+        String name = oname.getKeyProperty("name");
+        name = name.substring(2);
+        int i = name.indexOf("/");
+        String hostName = name.substring(0,i);
+        String path = name.substring(i);
+        ObjectName deployer = new ObjectName(domain+":type=Deployer,host="+
+                                             hostName);
+        String pathStr = getPathStr(path);
+        if(mserver.isRegistered(deployer)) {
+            mserver.invoke(deployer,"addServiced",
+                           new Object[]{pathStr},
+                           new String[] {"java.lang.String"});
+            mserver.invoke(deployer,"unmanageApp",
+                           new Object[] {pathStr},
+                           new String[] {"java.lang.String"});
+            mserver.invoke(deployer,"removeServiced",
+                           new Object[] {pathStr},
+                           new String[] {"java.lang.String"});
+        } else {
+            log.warn("Deployer not found for "+hostName);
+            Host host = (Host) engine.findChild(hostName);
+            Context context = (Context) host.findChild(pathStr);
+            // Remove this component from its parent component
+            host.removeChild(context);
+            if(context instanceof StandardContext)
+            try {
+                ((StandardContext)context).destroy();
+            } catch (Exception e) {
+                log.warn("Error during context [" + context.getName() + "] destroy ", e);
+           }
+   
+        }
+
+    }
+
+
+    /**
+     * Remove an existing Host.
+     *
+     * @param name MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeHost(String name) throws Exception {
+
+        // Acquire a reference to the component to be removed
+        ObjectName oname = new ObjectName(name);
+        String hostName = oname.getKeyProperty("host");
+        Service service = getService(oname);
+        Engine engine = (Engine) service.getContainer();
+        Host host = (Host) engine.findChild(hostName);
+
+        // Remove this component from its parent component
+        if(host!=null) {
+            engine.removeChild(host);
+        }
+    }
+
+
+    /**
+     * Remove an existing Loader.
+     *
+     * @param name MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeLoader(String name) throws Exception {
+
+        ObjectName oname = new ObjectName(name);
+        // Acquire a reference to the component to be removed
+        ContainerBase container = getParentContainerFromChild(oname);    
+        container.setLoader(null);
+        
+    }
+
+
+    /**
+     * Remove an existing Manager.
+     *
+     * @param name MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeManager(String name) throws Exception {
+
+        ObjectName oname = new ObjectName(name);
+        // Acquire a reference to the component to be removed
+        ContainerBase container = getParentContainerFromChild(oname);    
+        container.setManager(null);
+
+    }
+
+
+    /**
+     * Remove an existing Realm.
+     *
+     * @param name MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeRealm(String name) throws Exception {
+
+        ObjectName oname = new ObjectName(name);
+        // Acquire a reference to the component to be removed
+        ContainerBase container = getParentContainerFromChild(oname); 
+        container.setRealm(null);
+    }
+
+
+    /**
+     * Remove an existing Service.
+     *
+     * @param name MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeService(String name) throws Exception {
+
+        if (!(container instanceof Server)) {
+            throw new Exception();
+        }
+        
+        // Acquire a reference to the component to be removed
+        ObjectName oname = new ObjectName(name);
+        Service service = getService(oname); 
+        ((Server) container).removeService(service);
+    }
+
+
+    /**
+     * Remove an existing Valve.
+     *
+     * @param name MBean Name of the component to remove
+     *
+     * @exception Exception if a component cannot be removed
+     */
+    public void removeValve(String name) throws Exception {
+
+        // Acquire a reference to the component to be removed
+        ObjectName oname = new ObjectName(name);
+        ContainerBase container = getParentContainerFromChild(oname);
+        Valve[] valves = container.getPipeline().getValves();
+        for (int i = 0; i < valves.length; i++) {
+            ObjectName voname = ((ValveBase) valves[i]).getObjectName();
+            if (voname.equals(oname)) {
+                container.getPipeline().removeValve(valves[i]);
+            }
+        }
+    }
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MBeanUtils.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MBeanUtils.java
new file mode 100644
index 0000000..6f4e355
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MBeanUtils.java
@@ -0,0 +1,1698 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import java.util.Hashtable;
+import java.util.Set;
+
+import javax.management.DynamicMBean;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.catalina.Contained;
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Group;
+import org.apache.catalina.Host;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Role;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.User;
+import org.apache.catalina.UserDatabase;
+import org.apache.catalina.Valve;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.deploy.ContextEnvironment;
+import org.apache.catalina.deploy.ContextResource;
+import org.apache.catalina.deploy.ContextResourceLink;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.catalina.util.ContextName;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.coyote.ProtocolHandler;
+import org.apache.coyote.ajp.AjpAprProtocol;
+import org.apache.coyote.ajp.AjpProtocol;
+import org.apache.coyote.http11.Http11AprProtocol;
+import org.apache.coyote.http11.Http11NioProtocol;
+import org.apache.coyote.http11.Http11Protocol;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+
+/**
+ * Public utility methods in support of the server side MBeans implementation.
+ *
+ * @author Craig R. McClanahan
+ * @author Amy Roh
+ * @version $Id: MBeanUtils.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class MBeanUtils {
+
+    // ------------------------------------------------------- Static Variables
+
+    /**
+     * The set of exceptions to the normal rules used by
+     * <code>createManagedBean()</code>.  The first element of each pair
+     * is a class name, and the second element is the managed bean name.
+     */
+    private static String exceptions[][] = {
+        { "org.apache.catalina.users.MemoryGroup",
+          "Group" },
+        { "org.apache.catalina.users.MemoryRole",
+          "Role" },
+        { "org.apache.catalina.users.MemoryUser",
+          "User" },
+    };
+
+
+    /**
+     * The configuration information registry for our managed beans.
+     */
+    private static Registry registry = createRegistry();
+
+
+    /**
+     * The <code>MBeanServer</code> for this application.
+     */
+    private static MBeanServer mserver = createServer();
+
+
+    // --------------------------------------------------------- Static Methods
+
+    /**
+     * Create and return the name of the <code>ManagedBean</code> that
+     * corresponds to this Catalina component.
+     *
+     * @param component The component for which to create a name
+     */
+    static String createManagedName(Object component) {
+
+        // Deal with exceptions to the standard rule
+        String className = component.getClass().getName();
+        for (int i = 0; i < exceptions.length; i++) {
+            if (className.equals(exceptions[i][0])) {
+                return (exceptions[i][1]);
+            }
+        }
+
+        // Perform the standard transformation
+        int period = className.lastIndexOf('.');
+        if (period >= 0)
+            className = className.substring(period + 1);
+        return (className);
+
+    }
+
+    
+    /**
+     * Create, register, and return an MBean for this
+     * <code>ContextEnvironment</code> object.
+     *
+     * @param environment The ContextEnvironment to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public static DynamicMBean createMBean(ContextEnvironment environment)
+        throws Exception {
+
+        String mname = createManagedName(environment);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(environment);
+        ObjectName oname = createObjectName(domain, environment);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>ContextResource</code> object.
+     *
+     * @param resource The ContextResource to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public static DynamicMBean createMBean(ContextResource resource)
+        throws Exception {
+
+        String mname = createManagedName(resource);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(resource);
+        ObjectName oname = createObjectName(domain, resource);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>ContextResourceLink</code> object.
+     *
+     * @param resourceLink The ContextResourceLink to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    public static DynamicMBean createMBean(ContextResourceLink resourceLink)
+        throws Exception {
+
+        String mname = createManagedName(resourceLink);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(resourceLink);
+        ObjectName oname = createObjectName(domain, resourceLink);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }    
+ 
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>Group</code> object.
+     *
+     * @param group The Group to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    static DynamicMBean createMBean(Group group)
+        throws Exception {
+
+        String mname = createManagedName(group);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(group);
+        ObjectName oname = createObjectName(domain, group);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>Loader</code> object.
+     *
+     * @param loader The Loader to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    static DynamicMBean createMBean(Loader loader)
+        throws Exception {
+
+        String mname = createManagedName(loader);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(loader);
+        ObjectName oname = createObjectName(domain, loader);
+        if( mserver.isRegistered( oname ))  {
+            // side effect: stop it
+            mserver.unregisterMBean( oname );
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>MBeanFactory</code> object.
+     *
+     * @param factory The MBeanFactory to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    static DynamicMBean createMBean(MBeanFactory factory)
+        throws Exception {
+
+        String mname = createManagedName(factory);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(factory);
+        ObjectName oname = createObjectName(domain, factory);
+        if( mserver.isRegistered(oname )) {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>NamingResources</code> object.
+     *
+     * @param resource The NamingResources to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    static DynamicMBean createMBean(NamingResources resource)
+        throws Exception {
+
+        String mname = createManagedName(resource);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(resource);
+        ObjectName oname = createObjectName(domain, resource);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+    
+    /**
+     * Create, register, and return an MBean for this
+     * <code>Role</code> object.
+     *
+     * @param role The Role to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    static DynamicMBean createMBean(Role role)
+        throws Exception {
+
+        String mname = createManagedName(role);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(role);
+        ObjectName oname = createObjectName(domain, role);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>User</code> object.
+     *
+     * @param user The User to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    static DynamicMBean createMBean(User user)
+        throws Exception {
+
+        String mname = createManagedName(user);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(user);
+        ObjectName oname = createObjectName(domain, user);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create, register, and return an MBean for this
+     * <code>UserDatabase</code> object.
+     *
+     * @param userDatabase The UserDatabase to be managed
+     *
+     * @exception Exception if an MBean cannot be created or registered
+     */
+    static DynamicMBean createMBean(UserDatabase userDatabase)
+        throws Exception {
+
+        String mname = createManagedName(userDatabase);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            Exception e = new Exception("ManagedBean is not found with "+mname);
+            throw new MBeanException(e);
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        DynamicMBean mbean = managed.createMBean(userDatabase);
+        ObjectName oname = createObjectName(domain, userDatabase);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        mserver.registerMBean(mbean, oname);
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Connector</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param connector The Connector to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                        Connector connector)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        try {
+            Object addressObj = IntrospectionUtils.getProperty(connector, "address");            
+            Integer port = (Integer)
+                IntrospectionUtils.getProperty(connector, "port");
+
+            StringBuilder sb = new StringBuilder(domain);
+            sb.append(":type=Connector");
+            sb.append(",port=");
+            sb.append(port);
+            if (addressObj != null) {
+                String address = addressObj.toString();
+                if (address.length() > 0) {
+                    sb.append(",address=");
+                    sb.append(ObjectName.quote(address));
+                }
+            }
+            name = new ObjectName(sb.toString());
+            return (name);
+        } catch (Exception e) {
+            MalformedObjectNameException mone =
+                new MalformedObjectNameException
+                ("Cannot create object name for " + connector);
+            mone.initCause(e);
+            throw mone;
+        }
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Context</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param context The Context to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Context context)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        Host host = (Host)context.getParent();
+        ContextName cn = new ContextName(context.getName());
+        name = new ObjectName(domain + ":j2eeType=WebModule,name=//" +
+                              host.getName()+ cn.getDisplayName() +
+                              ",J2EEApplication=none,J2EEServer=none");
+    
+        return (name);
+
+    }
+
+    
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Service</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param environment The ContextEnvironment to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    public static ObjectName createObjectName(String domain,
+                                              ContextEnvironment environment)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        Object container = 
+                environment.getNamingResources().getContainer();
+        if (container instanceof Server) {
+            name = new ObjectName(domain + ":type=Environment" + 
+                        ",resourcetype=Global,name=" + environment.getName());
+        } else if (container instanceof Context) {        
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            name = new ObjectName(domain + ":type=Environment" + 
+                        ",resourcetype=Context,context=" + cn.getDisplayName() + 
+                        ",host=" + host.getName() +
+                        ",name=" + environment.getName());
+        }        
+        return (name);
+
+    }
+    
+    
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>ContextResource</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param resource The ContextResource to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    public static ObjectName createObjectName(String domain,
+                                              ContextResource resource)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        String quotedResourceName = ObjectName.quote(resource.getName());
+        Object container = 
+                resource.getNamingResources().getContainer();
+        if (container instanceof Server) {        
+            name = new ObjectName(domain + ":type=Resource" +
+                        ",resourcetype=Global,class=" + resource.getType() + 
+                        ",name=" + quotedResourceName);
+        } else if (container instanceof Context) {                    
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            name = new ObjectName(domain + ":type=Resource" +
+                        ",resourcetype=Context,context=" + cn.getDisplayName() + 
+                        ",host=" + host.getName() +
+                        ",class=" + resource.getType() +
+                        ",name=" + quotedResourceName);
+        }
+        
+        return (name);
+
+    }
+  
+    
+     /**
+     * Create an <code>ObjectName</code> for this
+     * <code>ContextResourceLink</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param resourceLink The ContextResourceLink to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    public static ObjectName createObjectName(String domain,
+                                              ContextResourceLink resourceLink)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        String quotedResourceLinkName
+                = ObjectName.quote(resourceLink.getName());        
+        Object container = 
+                resourceLink.getNamingResources().getContainer();
+        if (container instanceof Server) {        
+            name = new ObjectName(domain + ":type=ResourceLink" +
+                        ",resourcetype=Global" + 
+                        ",name=" + quotedResourceLinkName);
+        } else if (container instanceof Context) {                    
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            name = new ObjectName(domain + ":type=ResourceLink" +
+                        ",resourcetype=Context,context=" + cn.getDisplayName() + 
+                        ",host=" + host.getName() +
+                        ",name=" + quotedResourceLinkName);
+        }
+        
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Engine</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param engine The Engine to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain, Engine engine)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=Engine");
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Group</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param group The Group to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Group group)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=Group,groupname=" +
+                              ObjectName.quote(group.getGroupname()) +
+                              ",database=" + group.getUserDatabase().getId());
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Host</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param host The Host to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Host host)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=Host,host=" +
+                              host.getName());
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Loader</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param loader The Loader to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Loader loader)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        Container container = loader.getContainer();
+
+        if (container instanceof Engine) {
+            name = new ObjectName(domain + ":type=Loader");
+        } else if (container instanceof Host) {
+            name = new ObjectName(domain + ":type=Loader,host=" +
+                              container.getName());
+        } else if (container instanceof Context) {
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            name = new ObjectName(domain + ":type=Loader,context=" +
+                    cn.getDisplayName() + ",host=" + host.getName());
+        }
+
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Manager</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param manager The Manager to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Manager manager)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        Container container = manager.getContainer();
+
+        if (container instanceof Engine) {
+            name = new ObjectName(domain + ":type=Manager");
+        } else if (container instanceof Host) {
+            name = new ObjectName(domain + ":type=Manager,host=" +
+                              container.getName());
+        } else if (container instanceof Context) {
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            name = new ObjectName(domain + ":type=Manager,context=" +
+                    cn.getDisplayName() + ",host=" + host.getName());
+        }
+
+        return (name);
+
+    }
+    
+    
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Server</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param resources The NamingResources to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              NamingResources resources)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        Object container = resources.getContainer();        
+        if (container instanceof Server) {        
+            name = new ObjectName(domain + ":type=NamingResources" + 
+                        ",resourcetype=Global");
+        } else if (container instanceof Context) {        
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            name = new ObjectName(domain + ":type=NamingResources" + 
+                        ",resourcetype=Context,context=" + cn.getDisplayName() + 
+                        ",host=" + host.getName());
+        }
+        
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>MBeanFactory</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param factory The MBeanFactory to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain, MBeanFactory factory)
+        throws MalformedObjectNameException {
+
+        ObjectName name = new ObjectName(domain + ":type=MBeanFactory");
+
+        return (name);
+
+    }
+
+    
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Realm</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param realm The Realm to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Realm realm)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        Container container = realm.getContainer();
+
+        if (container instanceof Engine) {
+            name = new ObjectName(domain + ":type=Realm");
+        } else if (container instanceof Host) {
+            name = new ObjectName(domain + ":type=Realm,host=" +
+                              container.getName());
+        } else if (container instanceof Context) {
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            name = new ObjectName(domain + ":type=Realm,context=" +
+                    cn.getDisplayName() + ",host=" + host.getName());
+        }
+
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Role</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param role The Role to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Role role)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=Role,rolename=" +
+                              role.getRolename() + ",database=" +
+                              role.getUserDatabase().getId());
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Server</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param server The Server to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain, Server server)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=Server");
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Service</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param service The Service to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              Service service)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=Service,serviceName=" + 
+                            service.getName());
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>User</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param user The User to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              User user)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=User,username=" +
+                              ObjectName.quote(user.getUsername())
+                              + ",database=" + user.getUserDatabase().getId());
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>UserDatabase</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param userDatabase The UserDatabase to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                              UserDatabase userDatabase)
+        throws MalformedObjectNameException {
+
+        ObjectName name = null;
+        name = new ObjectName(domain + ":type=UserDatabase,database=" +
+                              userDatabase.getId());
+        return (name);
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>Valve</code> object.
+     *
+     * @param domain Domain in which this name is to be created
+     * @param valve The Valve to be named
+     *
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    static ObjectName createObjectName(String domain,
+                                       Valve valve)
+        throws MalformedObjectNameException {
+        if( valve instanceof ValveBase ) {
+            ObjectName name=((ValveBase)valve).getObjectName();
+            if( name != null )
+                return name;
+        }
+
+        ObjectName name = null;
+        Container container = null;
+        String className=valve.getClass().getName();
+        int period = className.lastIndexOf('.');
+        if (period >= 0)
+            className = className.substring(period + 1);
+        if( valve instanceof Contained ) {
+            container = ((Contained)valve).getContainer();
+        }
+        if( container == null ) {
+            throw new MalformedObjectNameException(
+                               "Cannot create mbean for non-contained valve " +
+                               valve);
+        }        
+        if (container instanceof Engine) {
+            String local="";
+            int seq = getSeq(local);
+            String ext="";
+            if( seq > 0 ) {
+                ext=",seq=" + seq;
+            }
+            name = new ObjectName(domain + ":type=Valve,name=" + className + 
+                                    ext + local );
+        } else if (container instanceof Host) {
+            String local=",host=" +container.getName();
+            int seq = getSeq(local);
+            String ext="";
+            if( seq > 0 ) {
+                ext=",seq=" + seq;
+            }
+            name = new ObjectName(domain + ":type=Valve,name=" + className + 
+                                    ext + local );
+        } else if (container instanceof Context) {
+            Context context = ((Context)container);
+            ContextName cn = new ContextName(context.getName());
+            Container host = context.getParent();
+            String local=",context=" + cn.getDisplayName() + ",host=" +
+                    host.getName();
+            int seq = getSeq(local);
+            String ext="";
+            if( seq > 0 ) {
+                ext=",seq=" + seq;
+            }
+            name = new ObjectName(domain + ":type=Valve,name=" + className + 
+                                    ext + local );
+        }
+
+        return (name);
+
+    }
+
+    static Hashtable<String,int[]> seq = new Hashtable<String,int[]>();
+    static int getSeq( String key ) {
+        int i[]=seq.get( key );
+        if (i == null ) {
+            i=new int[1];
+            i[0]=0;
+            seq.put( key, i);
+        } else {
+            i[0]++;
+        }
+        return i[0];
+    }
+
+    /**
+     * Create and configure (if necessary) and return the registry of
+     * managed object descriptions.
+     */
+    public static synchronized Registry createRegistry() {
+
+        if (registry == null) {
+            registry = Registry.getRegistry(null, null);
+            ClassLoader cl = MBeanUtils.class.getClassLoader();
+
+            registry.loadDescriptors("org.apache.catalina.mbeans",  cl);
+            registry.loadDescriptors("org.apache.catalina.authenticator", cl);
+            registry.loadDescriptors("org.apache.catalina.core", cl);
+            registry.loadDescriptors("org.apache.catalina", cl);
+            registry.loadDescriptors("org.apache.catalina.deploy", cl);
+            registry.loadDescriptors("org.apache.catalina.loader", cl);
+            registry.loadDescriptors("org.apache.catalina.realm", cl);
+            registry.loadDescriptors("org.apache.catalina.session", cl);
+            registry.loadDescriptors("org.apache.catalina.startup", cl);
+            registry.loadDescriptors("org.apache.catalina.users", cl);
+            registry.loadDescriptors("org.apache.catalina.ha", cl);
+            registry.loadDescriptors("org.apache.catalina.connector", cl);
+            registry.loadDescriptors("org.apache.catalina.valves",  cl);
+        }
+        return (registry);
+
+    }
+
+
+    /**
+     * Create and configure (if necessary) and return the
+     * <code>MBeanServer</code> with which we will be
+     * registering our <code>DynamicMBean</code> implementations.
+     */
+    public static synchronized MBeanServer createServer() {
+
+        if (mserver == null) {
+            mserver = Registry.getRegistry(null, null).getMBeanServer();
+        }
+        return (mserver);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Connector</code> object.
+     *
+     * @param connector The Connector to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Connector connector, Service service)
+        throws Exception {
+
+        // domain is engine name
+        String domain = service.getContainer().getName();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, connector);
+        connector.setService(null);
+        if( mserver.isRegistered( oname ))  {
+            mserver.unregisterMBean(oname);
+        }
+        // Unregister associated request processor
+        String worker = null;
+        ProtocolHandler handler = connector.getProtocolHandler();
+        if (handler instanceof Http11Protocol) {
+            worker = ((Http11Protocol)handler).getName();
+        } else if (handler instanceof Http11NioProtocol) {
+            worker = ((Http11NioProtocol)handler).getName();
+        } else if (handler instanceof Http11AprProtocol) {
+            worker = ((Http11AprProtocol)handler).getName();
+        } else if (handler instanceof AjpProtocol) {
+            worker = ((AjpProtocol)handler).getName();
+        } else if (handler instanceof AjpAprProtocol) {
+            worker = ((AjpAprProtocol)handler).getName();
+        }
+        ObjectName query = new ObjectName(
+                domain + ":type=RequestProcessor,worker=" + worker + ",*");
+        Set<ObjectName> results = mserver.queryNames(query, null);
+        for(ObjectName result : results) {
+            mserver.unregisterMBean(result);
+        }
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Context</code> object.
+     *
+     * @param context The Context to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Context context)
+        throws Exception {
+
+        String domain = context.getParent().getParent().getName();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, context);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+    
+    /**
+     * Deregister the MBean for this
+     * <code>ContextEnvironment</code> object.
+     *
+     * @param environment The ContextEnvironment to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    public static void destroyMBean(ContextEnvironment environment)
+        throws Exception {
+
+        String mname = createManagedName(environment);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, environment);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+    
+    
+    /**
+     * Deregister the MBean for this
+     * <code>ContextResource</code> object.
+     *
+     * @param resource The ContextResource to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    public static void destroyMBean(ContextResource resource)
+        throws Exception {
+
+        // If this is a user database resource need to destroy groups, roles,
+        // users and UserDatabase mbean
+        if ("org.apache.catalina.UserDatabase".equals(resource.getType())) {
+            destroyMBeanUserDatabase(resource.getName());
+        }
+
+        String mname = createManagedName(resource);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, resource);
+        if( mserver.isRegistered(oname ))
+            mserver.unregisterMBean(oname);
+
+    }
+     
+    
+    /**
+     * Deregister the MBean for this
+     * <code>ContextResourceLink</code> object.
+     *
+     * @param resourceLink The ContextResourceLink to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    public static void destroyMBean(ContextResourceLink resourceLink)
+        throws Exception {
+
+        String mname = createManagedName(resourceLink);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, resourceLink);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }   
+    
+    /**
+     * Deregister the MBean for this
+     * <code>Engine</code> object.
+     *
+     * @param engine The Engine to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Engine engine)
+        throws Exception {
+        String domain = engine.getName();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, engine);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Group</code> object.
+     *
+     * @param group The Group to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Group group)
+        throws Exception {
+
+        String mname = createManagedName(group);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, group);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Host</code> object.
+     *
+     * @param host The Host to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Host host)
+        throws Exception {
+
+        String domain = host.getParent().getName();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, host);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Loader</code> object.
+     *
+     * @param loader The Loader to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Loader loader)
+        throws Exception {
+
+        String mname = createManagedName(loader);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, loader);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Manager</code> object.
+     *
+     * @param manager The Manager to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Manager manager)
+        throws Exception {
+
+        String mname = createManagedName(manager);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, manager);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+    
+    
+   /**
+     * Deregister the MBean for this
+     * <code>NamingResources</code> object.
+     *
+     * @param resources The NamingResources to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(NamingResources resources)
+        throws Exception {
+
+        String mname = createManagedName(resources);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, resources);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+    
+    
+    /**
+     * Deregister the MBean for this
+     * <code>Realm</code> object.
+     *
+     * @param realm The Realm to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Realm realm)
+        throws Exception {
+
+        String mname = createManagedName(realm);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, realm);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Role</code> object.
+     *
+     * @param role The Role to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Role role)
+        throws Exception {
+
+        String mname = createManagedName(role);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, role);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Server</code> object.
+     *
+     * @param server The Server to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Server server)
+        throws Exception {
+
+        String mname = createManagedName(server);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, server);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+        
+        // Global String cache - fixed name
+        oname = new ObjectName("Catalina:type=StringCache");
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+        // MBean Factory - fixed name
+        oname = new ObjectName("Catalina:type=MBeanFactory");
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Service</code> object.
+     *
+     * @param service The Service to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Service service)
+        throws Exception {
+
+        String mname = createManagedName(service);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, service);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>User</code> object.
+     *
+     * @param user The User to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(User user)
+        throws Exception {
+
+        String mname = createManagedName(user);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, user);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>UserDatabase</code> object.
+     *
+     * @param userDatabase The UserDatabase to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(UserDatabase userDatabase)
+        throws Exception {
+
+        String mname = createManagedName(userDatabase);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, userDatabase);
+        if( mserver.isRegistered(oname) )
+            mserver.unregisterMBean(oname);
+
+    }
+
+
+    /**
+     * Deregister the MBean for the
+     * <code>UserDatabase</code> object with this name.
+     *
+     * @param userDatabase The UserDatabase to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBeanUserDatabase(String userDatabase)
+        throws Exception {
+
+        ObjectName query = null;
+        Set<ObjectName> results = null;
+        
+        // Groups
+        query = new ObjectName(
+                "Users:type=Group,database=" + userDatabase + ",*");
+        results = mserver.queryNames(query, null);
+        for(ObjectName result : results) {
+            mserver.unregisterMBean(result);
+        }
+        
+        // Roles
+        query = new ObjectName(
+                "Users:type=Role,database=" + userDatabase + ",*");
+        results = mserver.queryNames(query, null);
+        for(ObjectName result : results) {
+            mserver.unregisterMBean(result);
+        }
+        
+        // Users
+        query = new ObjectName(
+                "Users:type=User,database=" + userDatabase + ",*");
+        results = mserver.queryNames(query, null);
+        for(ObjectName result : results) {
+            mserver.unregisterMBean(result);
+        }
+
+        // The database itself
+        ObjectName db = new ObjectName(
+                "Users:type=UserDatabase,database=" + userDatabase);
+        mserver.unregisterMBean(db);
+    }
+
+
+    /**
+     * Deregister the MBean for this
+     * <code>Valve</code> object.
+     *
+     * @param valve The Valve to be managed
+     *
+     * @exception Exception if an MBean cannot be deregistered
+     */
+    static void destroyMBean(Valve valve, Container container)
+        throws Exception {
+
+        ((Contained)valve).setContainer(container);
+        String mname = createManagedName(valve);
+        ManagedBean managed = registry.findManagedBean(mname);
+        if (managed == null) {
+            return;
+        }
+        String domain = managed.getDomain();
+        if (domain == null)
+            domain = mserver.getDefaultDomain();
+        ObjectName oname = createObjectName(domain, valve);
+        try {
+            ((Contained)valve).setContainer(null);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+        }
+        if( mserver.isRegistered(oname) ) {
+            mserver.unregisterMBean(oname);
+        }
+
+    }
+
+    
+    /**
+     * Determine the name of the domain to register MBeans for from a given
+     * Service.
+     * 
+     * @param service 
+     */
+    public static String getDomain(Service service) {
+        
+        // Null service -> return null
+        if (service == null) {
+            return null;
+        }
+        
+        String domain = null;
+        
+        Container engine = service.getContainer();
+        
+        // Use the engine name first
+        if (engine != null) {
+            domain = engine.getName();
+        }
+        
+        // No engine or no engine name, use the service name 
+        if (domain == null) {
+            domain = service.getName();
+        }
+        
+        // No service name, use null
+        return domain;
+    }
+    
+
+    /**
+     * Determine the name of the domain to register MBeans for from a given
+     * Container.
+     * 
+     * @param container
+     */
+    public static String getDomain(Container container) {
+        
+        String domain = null;
+        
+        Container c = container;
+        
+        while (!(c instanceof Engine) && c != null) {
+            c = c.getParent();
+        }
+        
+        if (c != null) {
+            domain = c.getName();
+        }
+        
+        return domain;
+    }
+
+    
+    /**
+     * Calculate the key properties string to be added to an object's
+     * {@link ObjectName} to indicate that it is associated with that container.
+     * 
+     * @param container The container the object is associated with 
+     * @return          A string suitable for appending to the ObjectName
+     */
+    public static String getContainerKeyProperties(Container container) {
+        
+        Container c = container;
+        StringBuilder keyProperties = new StringBuilder();
+        int containerCount = 0;
+        
+        // Work up container hierarchy, add a component to the name for
+        // each container
+        while (!(c instanceof Engine)) {
+            if (c instanceof Wrapper) {
+                keyProperties.append(",servlet=");
+                keyProperties.append(c.getName());
+            } else if (c instanceof Context) {
+                keyProperties.append(",context=");
+                ContextName cn = new ContextName(c.getName());
+                keyProperties.append(cn.getDisplayName());
+            } else if (c instanceof Host) {
+                keyProperties.append(",host=");
+                keyProperties.append(c.getName());
+            } else if (c == null) {
+                // May happen in unit testing and/or some embedding scenarios
+                keyProperties.append(",container");
+                keyProperties.append(containerCount++);
+                keyProperties.append("=null");
+                break;
+            } else {
+                // Should never happen...
+                keyProperties.append(",container");
+                keyProperties.append(containerCount++);
+                keyProperties.append('=');
+                keyProperties.append(c.getName());
+            }
+            c = c.getParent();
+        }
+
+        return keyProperties.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MemoryUserDatabaseMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MemoryUserDatabaseMBean.java
new file mode 100644
index 0000000..c445e22
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/MemoryUserDatabaseMBean.java
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.management.MBeanException;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+import org.apache.catalina.UserDatabase;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.users.MemoryUserDatabase</code> component.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryUserDatabaseMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class MemoryUserDatabaseMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public MemoryUserDatabaseMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The configuration information registry for our managed beans.
+     */
+    protected Registry registry = MBeanUtils.createRegistry();
+
+
+    /**
+     * The <code>ManagedBean</code> information describing this MBean.
+     */
+    protected ManagedBean managed =
+        registry.findManagedBean("MemoryUserDatabase");
+
+
+    /**
+     * The <code>ManagedBean</code> information describing Group MBeans.
+     */
+    protected ManagedBean managedGroup =
+        registry.findManagedBean("Group");
+
+
+    /**
+     * The <code>ManagedBean</code> information describing Group MBeans.
+     */
+    protected ManagedBean managedRole =
+        registry.findManagedBean("Role");
+
+
+    /**
+     * The <code>ManagedBean</code> information describing User MBeans.
+     */
+    protected ManagedBean managedUser =
+        registry.findManagedBean("User");
+
+
+    // ------------------------------------------------------------- Attributes
+
+
+    /**
+     * Return the MBean Names of all groups defined in this database.
+     */
+    public String[] getGroups() {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<Group> groups = database.getGroups();
+        while (groups.hasNext()) {
+            Group group = groups.next();
+            results.add(findGroup(group.getGroupname()));
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+
+    /**
+     * Return the MBean Names of all roles defined in this database.
+     */
+    public String[] getRoles() {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<Role> roles = database.getRoles();
+        while (roles.hasNext()) {
+            Role role = roles.next();
+            results.add(findRole(role.getRolename()));
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+
+    /**
+     * Return the MBean Names of all users defined in this database.
+     */
+    public String[] getUsers() {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<User> users = database.getUsers();
+        while (users.hasNext()) {
+            User user = users.next();
+            results.add(findUser(user.getUsername()));
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+
+    // ------------------------------------------------------------- Operations
+
+
+    /**
+     * Create a new Group and return the corresponding MBean Name.
+     *
+     * @param groupname Group name of the new group
+     * @param description Description of the new group
+     */
+    public String createGroup(String groupname, String description) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        Group group = database.createGroup(groupname, description);
+        try {
+            MBeanUtils.createMBean(group);
+        } catch (Exception e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Exception creating group " + group + " MBean");
+            iae.initCause(e);
+            throw iae;
+        }
+        return (findGroup(groupname));
+
+    }
+
+
+    /**
+     * Create a new Role and return the corresponding MBean Name.
+     *
+     * @param rolename Group name of the new group
+     * @param description Description of the new group
+     */
+    public String createRole(String rolename, String description) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        Role role = database.createRole(rolename, description);
+        try {
+            MBeanUtils.createMBean(role);
+        } catch (Exception e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Exception creating role " + role + " MBean");
+            iae.initCause(e);
+            throw iae;
+        }
+        return (findRole(rolename));
+
+    }
+
+
+    /**
+     * Create a new User and return the corresponding MBean Name.
+     *
+     * @param username User name of the new user
+     * @param password Password for the new user
+     * @param fullName Full name for the new user
+     */
+    public String createUser(String username, String password,
+                             String fullName) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        User user = database.createUser(username, password, fullName);
+        try {
+            MBeanUtils.createMBean(user);
+        } catch (Exception e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Exception creating user " + user + " MBean");
+            iae.initCause(e);
+            throw iae;
+        }
+        return (findUser(username));
+
+    }
+
+
+    /**
+     * Return the MBean Name for the specified group name (if any);
+     * otherwise return <code>null</code>.
+     *
+     * @param groupname Group name to look up
+     */
+    public String findGroup(String groupname) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        Group group = database.findGroup(groupname);
+        if (group == null) {
+            return (null);
+        }
+        try {
+            ObjectName oname =
+                MBeanUtils.createObjectName(managedGroup.getDomain(), group);
+            return (oname.toString());
+        } catch (MalformedObjectNameException e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Cannot create object name for group " + group);
+            iae.initCause(e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Return the MBean Name for the specified role name (if any);
+     * otherwise return <code>null</code>.
+     *
+     * @param rolename Role name to look up
+     */
+    public String findRole(String rolename) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        Role role = database.findRole(rolename);
+        if (role == null) {
+            return (null);
+        }
+        try {
+            ObjectName oname =
+                MBeanUtils.createObjectName(managedRole.getDomain(), role);
+            return (oname.toString());
+        } catch (MalformedObjectNameException e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Cannot create object name for role " + role);
+            iae.initCause(e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Return the MBean Name for the specified user name (if any);
+     * otherwise return <code>null</code>.
+     *
+     * @param username User name to look up
+     */
+    public String findUser(String username) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        User user = database.findUser(username);
+        if (user == null) {
+            return (null);
+        }
+        try {
+            ObjectName oname =
+                MBeanUtils.createObjectName(managedUser.getDomain(), user);
+            return (oname.toString());
+        } catch (MalformedObjectNameException e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Cannot create object name for user " + user);
+            iae.initCause(e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Remove an existing group and destroy the corresponding MBean.
+     *
+     * @param groupname Group name to remove
+     */
+    public void removeGroup(String groupname) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        Group group = database.findGroup(groupname);
+        if (group == null) {
+            return;
+        }
+        try {
+            MBeanUtils.destroyMBean(group);
+            database.removeGroup(group);
+        } catch (Exception e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Exception destroying group " + group + " MBean");
+            iae.initCause(e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Remove an existing role and destroy the corresponding MBean.
+     *
+     * @param rolename Role name to remove
+     */
+    public void removeRole(String rolename) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        Role role = database.findRole(rolename);
+        if (role == null) {
+            return;
+        }
+        try {
+            MBeanUtils.destroyMBean(role);
+            database.removeRole(role);
+        } catch (Exception e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Exception destroying role " + role + " MBean");
+            iae.initCause(e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Remove an existing user and destroy the corresponding MBean.
+     *
+     * @param username User name to remove
+     */
+    public void removeUser(String username) {
+
+        UserDatabase database = (UserDatabase) this.resource;
+        User user = database.findUser(username);
+        if (user == null) {
+            return;
+        }
+        try {
+            MBeanUtils.destroyMBean(user);
+            database.removeUser(user);
+        } catch (Exception e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Exception destroying user " + user + " MBean");
+            iae.initCause(e);
+            throw iae;
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/NamingResourcesMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/NamingResourcesMBean.java
new file mode 100644
index 0000000..43ad472
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/NamingResourcesMBean.java
@@ -0,0 +1,324 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+import java.util.ArrayList;
+
+import javax.management.MBeanException;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.catalina.deploy.ContextEnvironment;
+import org.apache.catalina.deploy.ContextResource;
+import org.apache.catalina.deploy.ContextResourceLink;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.deploy.NamingResources</code> component.</p>
+ *
+ * @author Amy Roh
+ * @version $Id: NamingResourcesMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class NamingResourcesMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public NamingResourcesMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+    
+    /**
+     * The configuration information registry for our managed beans.
+     */
+    protected Registry registry = MBeanUtils.createRegistry();
+
+
+    /**
+     * The <code>ManagedBean</code> information describing this MBean.
+     */
+    protected ManagedBean managed =
+        registry.findManagedBean("NamingResources");
+
+    // ------------------------------------------------------------- Attributes
+    
+
+    /**
+     * Return the MBean Names of the set of defined environment entries for  
+     * this web application
+     */
+    public String[] getEnvironments() {
+        ContextEnvironment[] envs = 
+                            ((NamingResources)this.resource).findEnvironments();
+        ArrayList<String> results = new ArrayList<String>();
+        for (int i = 0; i < envs.length; i++) {
+            try {
+                ObjectName oname =
+                    MBeanUtils.createObjectName(managed.getDomain(), envs[i]);
+                results.add(oname.toString());
+            } catch (MalformedObjectNameException e) {
+                IllegalArgumentException iae = new IllegalArgumentException
+                    ("Cannot create object name for environment " + envs[i]);
+                iae.initCause(e);
+                throw iae;
+            }
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+    
+    
+    /**
+     * Return the MBean Names of all the defined resource references for this
+     * application.
+     */
+    public String[] getResources() {
+        
+        ContextResource[] resources = 
+                            ((NamingResources)this.resource).findResources();
+        ArrayList<String> results = new ArrayList<String>();
+        for (int i = 0; i < resources.length; i++) {
+            try {
+                ObjectName oname =
+                    MBeanUtils.createObjectName(managed.getDomain(), resources[i]);
+                results.add(oname.toString());
+            } catch (MalformedObjectNameException e) {
+                IllegalArgumentException iae = new IllegalArgumentException
+                    ("Cannot create object name for resource " + resources[i]);
+                iae.initCause(e);
+                throw iae;
+            }
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+    
+    
+    /**
+     * Return the MBean Names of all the defined resource link references for 
+     * this application.
+     */
+    public String[] getResourceLinks() {
+        
+        ContextResourceLink[] resourceLinks = 
+                            ((NamingResources)this.resource).findResourceLinks();
+        ArrayList<String> results = new ArrayList<String>();
+        for (int i = 0; i < resourceLinks.length; i++) {
+            try {
+                ObjectName oname =
+                    MBeanUtils.createObjectName(managed.getDomain(), resourceLinks[i]);
+                results.add(oname.toString());
+            } catch (MalformedObjectNameException e) {
+                IllegalArgumentException iae = new IllegalArgumentException
+                    ("Cannot create object name for resource " + resourceLinks[i]);
+                iae.initCause(e);
+                throw iae;
+            }
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+    // ------------------------------------------------------------- Operations
+
+
+    /**
+     * Add an environment entry for this web application.
+     *
+     * @param envName New environment entry name
+     * @param type The type of the new environment entry
+     * @param value The value of the new environment entry 
+     */
+    public String addEnvironment(String envName, String type, String value) 
+        throws MalformedObjectNameException {
+
+        NamingResources nresources = (NamingResources) this.resource;
+        if (nresources == null) {
+            return null;
+        }
+        ContextEnvironment env = nresources.findEnvironment(envName);
+        if (env != null) {
+            throw new IllegalArgumentException
+                ("Invalid environment name - already exists '" + envName + "'");
+        }
+        env = new ContextEnvironment();
+        env.setName(envName);
+        env.setType(type);
+        env.setValue(value);
+        nresources.addEnvironment(env);
+        
+        // Return the corresponding MBean name
+        ManagedBean managed = registry.findManagedBean("ContextEnvironment");
+        ObjectName oname =
+            MBeanUtils.createObjectName(managed.getDomain(), env);
+        return (oname.toString());
+        
+    }
+
+    
+    /**
+     * Add a resource reference for this web application.
+     *
+     * @param resourceName New resource reference name
+     * @param type New resource reference type
+     */
+    public String addResource(String resourceName, String type) 
+        throws MalformedObjectNameException {
+        
+        NamingResources nresources = (NamingResources) this.resource;
+        if (nresources == null) {
+            return null;
+        }
+        ContextResource resource = nresources.findResource(resourceName);
+        if (resource != null) {
+            throw new IllegalArgumentException
+                ("Invalid resource name - already exists'" + resourceName + "'");
+        }
+        resource = new ContextResource();
+        resource.setName(resourceName);
+        resource.setType(type);
+        nresources.addResource(resource);
+        
+        // Return the corresponding MBean name
+        ManagedBean managed = registry.findManagedBean("ContextResource");
+        ObjectName oname =
+            MBeanUtils.createObjectName(managed.getDomain(), resource);
+        return (oname.toString());
+    }
+
+    
+    /**
+     * Add a resource link reference for this web application.
+     *
+     * @param resourceLinkName New resource link reference name
+     * @param type New resource link reference type
+     */
+    public String addResourceLink(String resourceLinkName, String type)
+        throws MalformedObjectNameException {
+        
+        NamingResources nresources = (NamingResources) this.resource;
+        if (nresources == null) {
+            return null;
+        }
+        ContextResourceLink resourceLink = 
+                            nresources.findResourceLink(resourceLinkName);
+        if (resourceLink != null) {
+            throw new IllegalArgumentException
+                ("Invalid resource link name - already exists'" + 
+                resourceLinkName + "'");
+        }
+        resourceLink = new ContextResourceLink();
+        resourceLink.setName(resourceLinkName);
+        resourceLink.setType(type);
+        nresources.addResourceLink(resourceLink);
+        
+        // Return the corresponding MBean name
+        ManagedBean managed = registry.findManagedBean("ContextResourceLink");
+        ObjectName oname =
+            MBeanUtils.createObjectName(managed.getDomain(), resourceLink);
+        return (oname.toString());
+    }
+    
+    
+    /**
+     * Remove any environment entry with the specified name.
+     *
+     * @param envName Name of the environment entry to remove
+     */
+    public void removeEnvironment(String envName) {
+
+        NamingResources nresources = (NamingResources) this.resource;
+        if (nresources == null) {
+            return;
+        }
+        ContextEnvironment env = nresources.findEnvironment(envName);
+        if (env == null) {
+            throw new IllegalArgumentException
+                ("Invalid environment name '" + envName + "'");
+        }
+        nresources.removeEnvironment(envName);
+
+    }
+    
+    
+    /**
+     * Remove any resource reference with the specified name.
+     *
+     * @param resourceName Name of the resource reference to remove
+     */
+    public void removeResource(String resourceName) {
+
+        resourceName = ObjectName.unquote(resourceName);
+        NamingResources nresources = (NamingResources) this.resource;
+        if (nresources == null) {
+            return;
+        }
+        ContextResource resource = nresources.findResource(resourceName);
+        if (resource == null) {
+            throw new IllegalArgumentException
+                ("Invalid resource name '" + resourceName + "'");
+        }
+        nresources.removeResource(resourceName);
+    
+    }
+    
+    
+    /**
+     * Remove any resource link reference with the specified name.
+     *
+     * @param resourceLinkName Name of the resource link reference to remove
+     */
+    public void removeResourceLink(String resourceLinkName) {
+
+        resourceLinkName = ObjectName.unquote(resourceLinkName);
+        NamingResources nresources = (NamingResources) this.resource;
+        if (nresources == null) {
+            return;
+        }
+        ContextResourceLink resourceLink = 
+                            nresources.findResourceLink(resourceLinkName);
+        if (resourceLink == null) {
+            throw new IllegalArgumentException
+                ("Invalid resource Link name '" + resourceLinkName + "'");
+        }
+        nresources.removeResourceLink(resourceLinkName);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/RoleMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/RoleMBean.java
new file mode 100644
index 0000000..e28b577
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/RoleMBean.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import javax.management.MBeanException;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.Role</code> component.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: RoleMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class RoleMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public RoleMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The configuration information registry for our managed beans.
+     */
+    protected Registry registry = MBeanUtils.createRegistry();
+
+
+    /**
+     * The <code>ManagedBean</code> information describing this MBean.
+     */
+    protected ManagedBean managed = registry.findManagedBean("Role");
+
+
+    // ------------------------------------------------------------- Attributes
+
+
+    // ------------------------------------------------------------- Operations
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ServiceMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ServiceMBean.java
new file mode 100644
index 0000000..4f23e17
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/ServiceMBean.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+
+import org.apache.catalina.Executor;
+import org.apache.catalina.Service;
+import org.apache.catalina.connector.Connector;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+
+public class ServiceMBean extends BaseModelMBean {
+
+    public ServiceMBean() 
+        throws MBeanException, RuntimeOperationsException {
+        
+        super();
+        
+    }
+    
+    /**
+     * Add a new Connector to the set of defined Connectors, and associate it
+     * with this Service's Container.
+     *
+     * @param address The IP address on which to bind
+     * @param port TCP port number to listen on
+     * @param isAjp Create a AJP/1.3 Connector
+     * @param isSSL Create a secure Connector
+     * 
+     * @throws MBeanException 
+     */
+    public void addConnector(String address, int port, boolean isAjp, boolean isSSL) throws MBeanException {
+        
+        Service service; 
+        try {
+            service = (Service)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        Connector connector = new Connector();
+        if ((address!=null) && (address.length()>0)) {
+            connector.setProperty("address", address);
+        }
+        
+        connector.setPort(port);
+        connector.setProtocol(isAjp ? "AJP/1.3" : "HTTP/1.1");
+        connector.setSecure(isSSL);
+        connector.setScheme(isSSL ? "https" : "http");
+
+        service.addConnector(connector);       
+
+    }
+    
+    /**
+     * Adds a named executor to the service
+     * @param type Classname of the Executor to be added
+     * @throws MBeanException 
+     */
+    public void addExecutor(String type) throws MBeanException {
+        
+        Service service; 
+        try {
+            service = (Service)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        Executor executor;
+        try {
+             executor = (Executor)Class.forName(type).newInstance();
+        } catch (InstantiationException e) {
+            throw new MBeanException(e);
+        } catch (IllegalAccessException e) {
+            throw new MBeanException(e);
+        } catch (ClassNotFoundException e) {
+            throw new MBeanException(e);
+        }
+        
+        service.addExecutor(executor);
+        
+    }
+    
+    /**
+     * Find and return the set of Connectors associated with this Service.
+     * @throws MBeanException 
+     */
+    public String[] findConnectors() throws MBeanException {
+        
+        Service service; 
+        try {
+            service = (Service)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        Connector[] connectors = service.findConnectors(); 
+        String[] str = new String[connectors.length];
+        
+        for(int i=0; i< connectors.length; i++){
+            str[i] = connectors[i].toString();
+        }
+        
+        return str;
+
+    }
+    
+    /**
+     * Retrieves all executors
+     * @throws MBeanException 
+     */
+    public String[] findExecutors() throws MBeanException {
+
+        Service service; 
+        try {
+            service = (Service)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        Executor[] executors = service.findExecutors();
+        String[] str = new String[executors.length];
+        
+        for(int i=0; i< executors.length; i++){
+            str[i] = executors[i].toString();
+        }
+        
+        return str;
+    }
+    
+    /**
+     * Retrieves executor by name
+     * @param name Name of the executor to be retrieved
+     * @throws MBeanException 
+     */
+    public String getExecutor(String name) throws MBeanException{
+       
+        Service service; 
+        try {
+            service = (Service)getManagedResource();
+        } catch (InstanceNotFoundException e) {
+            throw new MBeanException(e);
+        } catch (RuntimeOperationsException e) {
+            throw new MBeanException(e);
+        } catch (InvalidTargetObjectTypeException e) {
+            throw new MBeanException(e);
+        }
+        
+        Executor executor = service.getExecutor(name);
+        return executor.toString();
+        
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/UserMBean.java b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/UserMBean.java
new file mode 100644
index 0000000..0c9e5b4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/UserMBean.java
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.mbeans;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.management.MBeanException;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * <p>A <strong>ModelMBean</strong> implementation for the
+ * <code>org.apache.catalina.User</code> component.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: UserMBean.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class UserMBean extends BaseModelMBean {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a <code>ModelMBean</code> with default
+     * <code>ModelMBeanInfo</code> information.
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception RuntimeOperationsException if an IllegalArgumentException
+     *  occurs
+     */
+    public UserMBean()
+        throws MBeanException, RuntimeOperationsException {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The configuration information registry for our managed beans.
+     */
+    protected Registry registry = MBeanUtils.createRegistry();
+
+
+    /**
+     * The <code>ManagedBean</code> information describing this MBean.
+     */
+    protected ManagedBean managed =
+        registry.findManagedBean("User");
+
+
+    // ------------------------------------------------------------- Attributes
+
+
+    /**
+     * Return the MBean Names of all groups this user is a member of.
+     */
+    public String[] getGroups() {
+
+        User user = (User) this.resource;
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<Group> groups = user.getGroups();
+        while (groups.hasNext()) {
+            Group group = null;
+            try {
+                group = groups.next();
+                ObjectName oname =
+                    MBeanUtils.createObjectName(managed.getDomain(), group);
+                results.add(oname.toString());
+            } catch (MalformedObjectNameException e) {
+                IllegalArgumentException iae = new IllegalArgumentException
+                    ("Cannot create object name for group " + group);
+                iae.initCause(e);
+                throw iae;
+            }
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+
+    /**
+     * Return the MBean Names of all roles assigned to this user.
+     */
+    public String[] getRoles() {
+
+        User user = (User) this.resource;
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<Role> roles = user.getRoles();
+        while (roles.hasNext()) {
+            Role role = null;
+            try {
+                role = roles.next();
+                ObjectName oname =
+                    MBeanUtils.createObjectName(managed.getDomain(), role);
+                results.add(oname.toString());
+            } catch (MalformedObjectNameException e) {
+                IllegalArgumentException iae = new IllegalArgumentException
+                    ("Cannot create object name for role " + role);
+                iae.initCause(e);
+                throw iae;
+            }
+        }
+        return results.toArray(new String[results.size()]);
+
+    }
+
+
+    // ------------------------------------------------------------- Operations
+
+
+    /**
+     * Add a new {@link Group} to those this user belongs to.
+     *
+     * @param groupname Group name of the new group
+     */
+    public void addGroup(String groupname) {
+
+        User user = (User) this.resource;
+        if (user == null) {
+            return;
+        }
+        Group group = user.getUserDatabase().findGroup(groupname);
+        if (group == null) {
+            throw new IllegalArgumentException
+                ("Invalid group name '" + groupname + "'");
+        }
+        user.addGroup(group);
+
+    }
+
+
+    /**
+     * Add a new {@link Role} to those this user belongs to.
+     *
+     * @param rolename Role name of the new role
+     */
+    public void addRole(String rolename) {
+
+        User user = (User) this.resource;
+        if (user == null) {
+            return;
+        }
+        Role role = user.getUserDatabase().findRole(rolename);
+        if (role == null) {
+            throw new IllegalArgumentException
+                ("Invalid role name '" + rolename + "'");
+        }
+        user.addRole(role);
+
+    }
+
+
+    /**
+     * Remove a {@link Group} from those this user belongs to.
+     *
+     * @param groupname Group name of the old group
+     */
+    public void removeGroup(String groupname) {
+
+        User user = (User) this.resource;
+        if (user == null) {
+            return;
+        }
+        Group group = user.getUserDatabase().findGroup(groupname);
+        if (group == null) {
+            throw new IllegalArgumentException
+                ("Invalid group name '" + groupname + "'");
+        }
+        user.removeGroup(group);
+
+    }
+
+
+    /**
+     * Remove a {@link Role} from those this user belongs to.
+     *
+     * @param rolename Role name of the old role
+     */
+    public void removeRole(String rolename) {
+
+        User user = (User) this.resource;
+        if (user == null) {
+            return;
+        }
+        Role role = user.getUserDatabase().findRole(rolename);
+        if (role == null) {
+            throw new IllegalArgumentException
+                ("Invalid role name '" + rolename + "'");
+        }
+        user.removeRole(role);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/mbeans-descriptors.xml
new file mode 100644
index 0000000..d838422
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans/mbeans-descriptors.xml
@@ -0,0 +1,347 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<mbeans-descriptors>
+
+  <mbean         name="MBeanFactory"
+                 type="org.apache.catalina.mbeans.MBeanFactory"
+          description="Factory for MBeans and corresponding components"
+               domain="Catalina">
+
+    <!-- IMPLEMENTATION NOTE - all of the createXxxxx methods create a new  -->
+    <!-- component and attach it to Catalina's component tree.  The return  -->
+    <!-- value is the object name of the corresponding MBean for the new    -->
+    <!-- component.                                                         -->
+
+    <operation   name="createAccessLoggerValve"
+          description="Create a new AccessLoggerValve"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createAjpConnector"
+          description="Create a new AjpConnector"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+      <parameter name="address"
+          description="The IP address on which to bind"
+                 type="java.lang.String"/>
+      <parameter name="port"
+          description="TCP port number to listen on"
+                 type="int"/>
+    </operation>
+
+    <operation   name="createDataSourceRealm"
+          description="Create a new DataSource Realm"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+      <parameter name="dataSourceName"
+          description="The JNDI named JDBC DataSource"
+                 type="java.lang.String"/>
+      <parameter name="roleNameCol"
+          description="The column in the user role table that names a role"
+                 type="java.lang.String"/>
+      <parameter name="userCredCol"
+          description="The column in the user table that holds the user's
+                        credentials"
+                 type="java.lang.String"/>
+      <parameter name="userNameCol"
+          description="The column in the user table that holds the user's
+                        username"
+                 type="java.lang.String"/>
+      <parameter name="userRoleTable"
+          description="The table that holds the relation between user's and
+                        roles"
+                 type="java.lang.String"/>
+      <parameter name="userTable"
+          description="The table that holds user data"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createHttpConnector"
+          description="Create a new HttpConnector"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+     <parameter name="address"
+          description="The IP address on which to bind"
+                 type="java.lang.String"/>
+      <parameter name="port"
+          description="TCP port number to listen on"
+                 type="int"/>
+    </operation>
+
+    <operation   name="createHttpsConnector"
+          description="Create a new HttpsConnector"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+      <parameter name="address"
+          description="The IP address on which to bind"
+                 type="java.lang.String"/>
+      <parameter name="port"
+          description="TCP port number to listen on"
+                 type="int"/>
+    </operation>
+
+    <operation   name="createJDBCRealm"
+          description="Create a new JDBC Realm"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createJNDIRealm"
+          description="Create a new JNDI Realm"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createMemoryRealm"
+          description="Create a new Memory Realm"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="createUserDatabaseRealm"
+          description="Create a new  UserDatabaseRealm"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+      <parameter name="resourceName"
+          description="Global JNDI resource name of the associated"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createRemoteAddrValve"
+          description="Create a new Remote Address Filter Valve"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createRemoteHostValve"
+          description="Create a new Remote Host Filter Valve"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createSingleSignOn"
+          description="Create a new Single Sign On Valve"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createStandardContext"
+          description="Create a new StandardContext"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+      <parameter name="path"
+          description="The context path for this Context"
+                 type="java.lang.String"/>
+      <parameter name="docBase"
+          description="Document base directory (or WAR) for ths Context"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createStandardServiceEngine"
+          description="Create a new StandardService and StandardEngine"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="domain"
+          description="Domain used for MBeans associated with the new Service"
+                 type="java.lang.String"/>
+      <parameter name="defaultHost"
+          description="Default host name for the new Engine"
+                 type="java.lang.String"/>
+      <parameter name="baseDir"
+          description="Base directory value for the new Engine"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createStandardHost"
+          description="Create a new StandardHost"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+      <parameter name="name"
+          description="Unique name of this Host"
+                 type="java.lang.String"/>
+      <parameter name="appBase"
+          description="set host deployment directory"
+                 type="java.lang.String"/>
+      <parameter name="autoDeploy"
+          description="The auto deploy flag for this Host"
+               type="boolean"/>           
+      <parameter name="deployOnStartup"
+          description="The deploy on startup flag for this Host"
+               type="boolean"/>
+      <parameter name="deployXML"
+          description="deploy Context XML config files property"
+               type="boolean"/> 
+      <parameter name="unpackWARs"
+          description="Unpack WARs property"
+               type="boolean"/>
+    </operation>
+
+
+    <operation   name="createStandardManager"
+          description="Create a new StandardManager"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createUserDatabaseRealm"
+          description="Create a new UserDatabase Realm"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+      <parameter name="resourceName"
+          description="Global JNDI resource name of our UserDatabase instance"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createWebappLoader"
+          description="Create a new Web Application Loader"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="parent"
+          description="MBean Name of the associated parent component"
+                 type="java.lang.String"/>
+    </operation>
+
+    <!-- IMPLEMENTATION NOTE - all of the removeXxxxx methods cause the     -->
+    <!-- corresponding Catalina component (and any related child            -->
+    <!-- components to be stopped (if necessary) and removed, and the       -->
+    <!-- corresponding MBeans to be destroyed.                              -->
+
+    <operation   name="removeConnector"
+          description="Remove an existing Connector"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeContext"
+          description="Remove an existing Context"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeHost"
+          description="Remove an existing Host"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeLoader"
+          description="Remove an existing Loader"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeManager"
+          description="Remove an existing Manager"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeRealm"
+          description="Remove an existing Realm"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeService"
+          description="Remove an existing Service"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeValve"
+          description="Remove an existing Valve"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="name"
+          description="MBean Name of the component to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ByteArrayServletOutputStream.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ByteArrayServletOutputStream.java
new file mode 100644
index 0000000..1ddb737
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ByteArrayServletOutputStream.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ssi;
+
+import java.io.ByteArrayOutputStream;
+
+import javax.servlet.ServletOutputStream;
+
+
+/**
+ * Class that extends ServletOuputStream, used as a wrapper from within
+ * <code>SsiInclude</code>
+ *
+ * @author Bip Thelin
+ * @version $Id: ByteArrayServletOutputStream.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ * @see ServletOutputStream and ByteArrayOutputStream
+ */
+public class ByteArrayServletOutputStream extends ServletOutputStream {
+    /**
+     * Our buffer to hold the stream.
+     */
+    protected ByteArrayOutputStream buf = null;
+
+
+    /**
+     * Construct a new ServletOutputStream.
+     */
+    public ByteArrayServletOutputStream() {
+        buf = new ByteArrayOutputStream();
+    }
+
+
+    /**
+     * @return the byte array.
+     */
+    public byte[] toByteArray() {
+        return buf.toByteArray();
+    }
+
+
+    /**
+     * Write to our buffer.
+     *
+     * @param b The parameter to write
+     */
+    @Override
+    public void write(int b) {
+        buf.write(b);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ExpressionParseTree.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ExpressionParseTree.java
new file mode 100644
index 0000000..0528632
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ExpressionParseTree.java
@@ -0,0 +1,410 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.text.ParseException;
+import java.util.LinkedList;
+import java.util.List;
+/**
+ * Represents a parsed expression.
+ * 
+ * @version $Revision: 1.1 $
+ * @author Paul Speed
+ */
+public class ExpressionParseTree {
+    /**
+     * Contains the current set of completed nodes. This is a workspace for the
+     * parser.
+     */
+    private LinkedList<Node> nodeStack = new LinkedList<Node>();
+    /**
+     * Contains operator nodes that don't yet have values. This is a workspace
+     * for the parser.
+     */
+    private LinkedList<OppNode> oppStack = new LinkedList<OppNode>();
+    /**
+     * The root node after the expression has been parsed.
+     */
+    private Node root;
+    /**
+     * The SSIMediator to use when evaluating the expressions.
+     */
+    private SSIMediator ssiMediator;
+
+
+    /**
+     * Creates a new parse tree for the specified expression.
+     */
+    public ExpressionParseTree(String expr, SSIMediator ssiMediator)
+            throws ParseException {
+        this.ssiMediator = ssiMediator;
+        parseExpression(expr);
+    }
+
+
+    /**
+     * Evaluates the tree and returns true or false. The specified SSIMediator
+     * is used to resolve variable references.
+     */
+    public boolean evaluateTree() {
+        return root.evaluate();
+    }
+
+
+    /**
+     * Pushes a new operator onto the opp stack, resolving existing opps as
+     * needed.
+     */
+    private void pushOpp(OppNode node) {
+        // If node is null then it's just a group marker
+        if (node == null) {
+            oppStack.add(0, node);
+            return;
+        }
+        while (true) {
+            if (oppStack.size() == 0) break;
+            OppNode top = oppStack.get(0);
+            // If the top is a spacer then don't pop
+            // anything
+            if (top == null) break;
+            // If the top node has a lower precedence then
+            // let it stay
+            if (top.getPrecedence() < node.getPrecedence()) break;
+            // Remove the top node
+            oppStack.remove(0);
+            // Let it fill its branches
+            top.popValues(nodeStack);
+            // Stick it on the resolved node stack
+            nodeStack.add(0, top);
+        }
+        // Add the new node to the opp stack
+        oppStack.add(0, node);
+    }
+
+
+    /**
+     * Resolves all pending opp nodes on the stack until the next group marker
+     * is reached.
+     */
+    private void resolveGroup() {
+        OppNode top = null;
+        while ((top = oppStack.remove(0)) != null) {
+            // Let it fill its branches
+            top.popValues(nodeStack);
+            // Stick it on the resolved node stack
+            nodeStack.add(0, top);
+        }
+    }
+
+
+    /**
+     * Parses the specified expression into a tree of parse nodes.
+     */
+    private void parseExpression(String expr) throws ParseException {
+        StringNode currStringNode = null;
+        // We cheat a little and start an artificial
+        // group right away. It makes finishing easier.
+        pushOpp(null);
+        ExpressionTokenizer et = new ExpressionTokenizer(expr);
+        while (et.hasMoreTokens()) {
+            int token = et.nextToken();
+            if (token != ExpressionTokenizer.TOKEN_STRING)
+                currStringNode = null;
+            switch (token) {
+                case ExpressionTokenizer.TOKEN_STRING :
+                    if (currStringNode == null) {
+                        currStringNode = new StringNode(et.getTokenValue());
+                        nodeStack.add(0, currStringNode);
+                    } else {
+                        // Add to the existing
+                        currStringNode.value.append(" ");
+                        currStringNode.value.append(et.getTokenValue());
+                    }
+                    break;
+                case ExpressionTokenizer.TOKEN_AND :
+                    pushOpp(new AndNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_OR :
+                    pushOpp(new OrNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_NOT :
+                    pushOpp(new NotNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_EQ :
+                    pushOpp(new EqualNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_NOT_EQ :
+                    pushOpp(new NotNode());
+                    // Sneak the regular node in. The NOT will
+                    // be resolved when the next opp comes along.
+                    oppStack.add(0, new EqualNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_RBRACE :
+                    // Closeout the current group
+                    resolveGroup();
+                    break;
+                case ExpressionTokenizer.TOKEN_LBRACE :
+                    // Push a group marker
+                    pushOpp(null);
+                    break;
+                case ExpressionTokenizer.TOKEN_GE :
+                    pushOpp(new NotNode());
+                    // Similar strategy to NOT_EQ above, except this
+                    // is NOT less than
+                    oppStack.add(0, new LessThanNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_LE :
+                    pushOpp(new NotNode());
+                    // Similar strategy to NOT_EQ above, except this
+                    // is NOT greater than
+                    oppStack.add(0, new GreaterThanNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_GT :
+                    pushOpp(new GreaterThanNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_LT :
+                    pushOpp(new LessThanNode());
+                    break;
+                case ExpressionTokenizer.TOKEN_END :
+                    break;
+            }
+        }
+        // Finish off the rest of the opps
+        resolveGroup();
+        if (nodeStack.size() == 0) {
+            throw new ParseException("No nodes created.", et.getIndex());
+        }
+        if (nodeStack.size() > 1) {
+            throw new ParseException("Extra nodes created.", et.getIndex());
+        }
+        if (oppStack.size() != 0) {
+            throw new ParseException("Unused opp nodes exist.", et.getIndex());
+        }
+        root = nodeStack.get(0);
+    }
+
+    /**
+     * A node in the expression parse tree.
+     */
+    private abstract class Node {
+        /**
+         * Return true if the node evaluates to true.
+         */
+        public abstract boolean evaluate();
+    }
+    /**
+     * A node the represents a String value
+     */
+    private class StringNode extends Node {
+        StringBuilder value;
+        String resolved = null;
+
+
+        public StringNode(String value) {
+            this.value = new StringBuilder(value);
+        }
+
+
+        /**
+         * Resolves any variable references and returns the value string.
+         */
+        public String getValue() {
+            if (resolved == null)
+                resolved = ssiMediator.substituteVariables(value.toString());
+            return resolved;
+        }
+
+
+        /**
+         * Returns true if the string is not empty.
+         */
+        @Override
+        public boolean evaluate() {
+            return !(getValue().length() == 0);
+        }
+
+
+        @Override
+        public String toString() {
+            return value.toString();
+        }
+    }
+
+    private static final int PRECEDENCE_NOT = 5;
+    private static final int PRECEDENCE_COMPARE = 4;
+    private static final int PRECEDENCE_LOGICAL = 1;
+
+    /**
+     * A node implementation that represents an operation.
+     */
+    private abstract class OppNode extends Node {
+        /**
+         * The left branch.
+         */
+        Node left;
+        /**
+         * The right branch.
+         */
+        Node right;
+
+
+        /**
+         * Returns a preference level suitable for comparison to other OppNode
+         * preference levels.
+         */
+        public abstract int getPrecedence();
+
+
+        /**
+         * Lets the node pop its own branch nodes off the front of the
+         * specified list. The default pulls two.
+         */
+        public void popValues(List<Node> values) {
+            right = values.remove(0);
+            left = values.remove(0);
+        }
+    }
+    private final class NotNode extends OppNode {
+        @Override
+        public boolean evaluate() {
+            return !left.evaluate();
+        }
+
+
+        @Override
+        public int getPrecedence() {
+            return PRECEDENCE_NOT;
+        }
+
+
+        /**
+         * Overridden to pop only one value.
+         */
+        @Override
+        public void popValues(List<Node> values) {
+            left = values.remove(0);
+        }
+
+
+        @Override
+        public String toString() {
+            return left + " NOT";
+        }
+    }
+    private final class AndNode extends OppNode {
+        @Override
+        public boolean evaluate() {
+            if (!left.evaluate()) // Short circuit
+                return false;
+            return right.evaluate();
+        }
+
+
+        @Override
+        public int getPrecedence() {
+            return PRECEDENCE_LOGICAL;
+        }
+
+
+        @Override
+        public String toString() {
+            return left + " " + right + " AND";
+        }
+    }
+    private final class OrNode extends OppNode {
+        @Override
+        public boolean evaluate() {
+            if (left.evaluate()) // Short circuit
+                return true;
+            return right.evaluate();
+        }
+
+
+        @Override
+        public int getPrecedence() {
+            return PRECEDENCE_LOGICAL;
+        }
+
+
+        @Override
+        public String toString() {
+            return left + " " + right + " OR";
+        }
+    }
+    private abstract class CompareNode extends OppNode {
+        protected int compareBranches() {
+            String val1 = ((StringNode)left).getValue();
+            String val2 = ((StringNode)right).getValue();
+            return val1.compareTo(val2);
+        }
+    }
+    private final class EqualNode extends CompareNode {
+        @Override
+        public boolean evaluate() {
+            return (compareBranches() == 0);
+        }
+
+
+        @Override
+        public int getPrecedence() {
+            return PRECEDENCE_COMPARE;
+        }
+
+
+        @Override
+        public String toString() {
+            return left + " " + right + " EQ";
+        }
+    }
+    private final class GreaterThanNode extends CompareNode {
+        @Override
+        public boolean evaluate() {
+            return (compareBranches() > 0);
+        }
+
+
+        @Override
+        public int getPrecedence() {
+            return PRECEDENCE_COMPARE;
+        }
+
+
+        @Override
+        public String toString() {
+            return left + " " + right + " GT";
+        }
+    }
+    private final class LessThanNode extends CompareNode {
+        @Override
+        public boolean evaluate() {
+            return (compareBranches() < 0);
+        }
+
+
+        @Override
+        public int getPrecedence() {
+            return PRECEDENCE_COMPARE;
+        }
+
+
+        @Override
+        public String toString() {
+            return left + " " + right + " LT";
+        }
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ExpressionTokenizer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ExpressionTokenizer.java
new file mode 100644
index 0000000..abab5a9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ExpressionTokenizer.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+/**
+ * Parses an expression string to return the individual tokens. This is
+ * patterned similar to the StreamTokenizer in the JDK but customized for SSI
+ * conditional expression parsing.
+ * 
+ * @version $Revision: 1.1 $
+ * @author Paul Speed
+ */
+public class ExpressionTokenizer {
+    public static final int TOKEN_STRING = 0;
+    public static final int TOKEN_AND = 1;
+    public static final int TOKEN_OR = 2;
+    public static final int TOKEN_NOT = 3;
+    public static final int TOKEN_EQ = 4;
+    public static final int TOKEN_NOT_EQ = 5;
+    public static final int TOKEN_RBRACE = 6;
+    public static final int TOKEN_LBRACE = 7;
+    public static final int TOKEN_GE = 8;
+    public static final int TOKEN_LE = 9;
+    public static final int TOKEN_GT = 10;
+    public static final int TOKEN_LT = 11;
+    public static final int TOKEN_END = 12;
+    private char[] expr;
+    private String tokenVal = null;
+    private int index;
+    private int length;
+
+
+    /**
+     * Creates a new parser for the specified expression.
+     */
+    public ExpressionTokenizer(String expr) {
+        this.expr = expr.trim().toCharArray();
+        this.length = this.expr.length;
+    }
+
+
+    /**
+     * Returns true if there are more tokens.
+     */
+    public boolean hasMoreTokens() {
+        return index < length;
+    }
+
+
+    /**
+     * Returns the current index for error reporting purposes.
+     */
+    public int getIndex() {
+        return index;
+    }
+
+
+    protected boolean isMetaChar(char c) {
+        return Character.isWhitespace(c) || c == '(' || c == ')' || c == '!'
+                || c == '<' || c == '>' || c == '|' || c == '&' || c == '=';
+    }
+
+
+    /**
+     * Returns the next token type and initializes any state variables
+     * accordingly.
+     */
+    public int nextToken() {
+        // Skip any leading white space
+        while (index < length && Character.isWhitespace(expr[index]))
+            index++;
+        // Clear the current token val
+        tokenVal = null;
+        if (index == length) return TOKEN_END; // End of string
+        int start = index;
+        char currentChar = expr[index];
+        char nextChar = (char)0;
+        index++;
+        if (index < length) nextChar = expr[index];
+        // Check for a known token start
+        switch (currentChar) {
+            case '(' :
+                return TOKEN_LBRACE;
+            case ')' :
+                return TOKEN_RBRACE;
+            case '=' :
+                return TOKEN_EQ;
+            case '!' :
+                if (nextChar == '=') {
+                    index++;
+                    return TOKEN_NOT_EQ;
+                }
+                return TOKEN_NOT;
+            case '|' :
+                if (nextChar == '|') {
+                    index++;
+                    return TOKEN_OR;
+                }
+                break;
+            case '&' :
+                if (nextChar == '&') {
+                    index++;
+                    return TOKEN_AND;
+                }
+                break;
+            case '>' :
+                if (nextChar == '=') {
+                    index++;
+                    return TOKEN_GE; // Greater than or equal
+                }
+                return TOKEN_GT; // Greater than
+            case '<' :
+                if (nextChar == '=') {
+                    index++;
+                    return TOKEN_LE; // Less than or equal
+                }
+                return TOKEN_LT; // Less than
+            default :
+                // Otherwise it's a string
+                break;
+        }
+        int end = index;
+        // If it's a quoted string then end is the next unescaped quote
+        if (currentChar == '"' || currentChar == '\'') {
+            char endChar = currentChar;
+            boolean escaped = false;
+            start++;
+            for (; index < length; index++) {
+                if (expr[index] == '\\' && !escaped) {
+                    escaped = true;
+                    continue;
+                }
+                if (expr[index] == endChar && !escaped) break;
+                escaped = false;
+            }
+            end = index;
+            index++; // Skip the end quote
+        } else {
+            // End is the next whitespace character
+            for (; index < length; index++) {
+                if (isMetaChar(expr[index])) break;
+            }
+            end = index;
+        }
+        // Extract the string from the array
+        this.tokenVal = new String(expr, start, end - start);
+        return TOKEN_STRING;
+    }
+
+
+    /**
+     * Returns the String value of the token if it was type TOKEN_STRING.
+     * Otherwise null is returned.
+     */
+    public String getTokenValue() {
+        return tokenVal;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ResponseIncludeWrapper.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ResponseIncludeWrapper.java
new file mode 100644
index 0000000..75d21c1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/ResponseIncludeWrapper.java
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ssi;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.tomcat.util.ExceptionUtils;
+
+/**
+ * A HttpServletResponseWrapper, used from
+ * <code>SSIServletExternalResolver</code>
+ * 
+ * @author Bip Thelin
+ * @author David Becker
+ * @version $Id: ResponseIncludeWrapper.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class ResponseIncludeWrapper extends HttpServletResponseWrapper {
+    /**
+     * The names of some headers we want to capture.
+     */
+    private static final String CONTENT_TYPE = "content-type";
+    private static final String LAST_MODIFIED = "last-modified";
+    private static final DateFormat RFC1123_FORMAT;
+    private static final String RFC1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";
+
+    protected long lastModified = -1;
+    private String contentType = null;
+
+    /**
+     * Our ServletOutputStream
+     */
+    protected ServletOutputStream captureServletOutputStream;
+    protected ServletOutputStream servletOutputStream;
+    protected PrintWriter printWriter;
+    
+    private ServletContext context;
+    private HttpServletRequest request;
+
+    static {
+        RFC1123_FORMAT = new SimpleDateFormat(RFC1123_PATTERN, Locale.US);
+        RFC1123_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+    
+    /**
+     * Initialize our wrapper with the current HttpServletResponse and
+     * ServletOutputStream.
+     * 
+     * @param context The servlet context
+     * @param request The HttpServletResponse to use
+     * @param response The response to use
+     * @param captureServletOutputStream The ServletOutputStream to use
+     */
+    public ResponseIncludeWrapper(ServletContext context, 
+            HttpServletRequest request, HttpServletResponse response,
+            ServletOutputStream captureServletOutputStream) {
+        super(response);
+        this.context = context;
+        this.request = request;
+        this.captureServletOutputStream = captureServletOutputStream;
+    }
+
+
+    /**
+     * Flush the servletOutputStream or printWriter ( only one will be non-null )
+     * This must be called after a requestDispatcher.include, since we can't
+     * assume that the included servlet flushed its stream.
+     */
+    public void flushOutputStreamOrWriter() throws IOException {
+        if (servletOutputStream != null) {
+            servletOutputStream.flush();
+        }
+        if (printWriter != null) {
+            printWriter.flush();
+        }
+    }
+
+
+    /**
+     * Return a printwriter, throws and exception if a OutputStream already
+     * been returned.
+     * 
+     * @return a PrintWriter object
+     * @exception java.io.IOException
+     *                if the outputstream already been called
+     */
+    @Override
+    public PrintWriter getWriter() throws java.io.IOException {
+        if (servletOutputStream == null) {
+            if (printWriter == null) {
+                setCharacterEncoding(getCharacterEncoding());
+                printWriter = new PrintWriter(
+                        new OutputStreamWriter(captureServletOutputStream,
+                                               getCharacterEncoding()));
+            }
+            return printWriter;
+        }
+        throw new IllegalStateException();
+    }
+
+
+    /**
+     * Return a OutputStream, throws and exception if a printwriter already
+     * been returned.
+     * 
+     * @return a OutputStream object
+     * @exception java.io.IOException
+     *                if the printwriter already been called
+     */
+    @Override
+    public ServletOutputStream getOutputStream() throws java.io.IOException {
+        if (printWriter == null) {
+            if (servletOutputStream == null) {
+                servletOutputStream = captureServletOutputStream;
+            }
+            return servletOutputStream;
+        }
+        throw new IllegalStateException();
+    }
+    
+    
+    /**
+     * Returns the value of the <code>last-modified</code> header field. The
+     * result is the number of milliseconds since January 1, 1970 GMT.
+     *
+     * @return the date the resource referenced by this
+     *   <code>ResponseIncludeWrapper</code> was last modified, or -1 if not
+     *   known.                                                             
+     */
+    public long getLastModified() {                                                                                                                                                           
+        if (lastModified == -1) {
+            // javadocs say to return -1 if date not known, if you want another
+            // default, put it here
+            return -1;
+        }
+        return lastModified;
+    }
+
+    /**
+     * Sets the value of the <code>last-modified</code> header field.
+     *
+     * @param lastModified The number of milliseconds since January 1, 1970 GMT.
+     */
+    public void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+        ((HttpServletResponse) getResponse()).setDateHeader(LAST_MODIFIED,
+                lastModified);
+    }
+
+    /**
+     * Returns the value of the <code>content-type</code> header field.
+     *
+     * @return the content type of the resource referenced by this
+     *   <code>ResponseIncludeWrapper</code>, or <code>null</code> if not known.
+     */
+    @Override
+    public String getContentType() {
+        if (contentType == null) {
+            String url = request.getRequestURI();
+            String mime = context.getMimeType(url);
+            if (mime != null) {
+                setContentType(mime);
+            } else {
+                // return a safe value
+                setContentType("application/x-octet-stream");
+            }
+        }
+        return contentType;
+    }
+    
+    /**
+     * Sets the value of the <code>content-type</code> header field.
+     *
+     * @param mime a mime type
+     */
+    @Override
+    public void setContentType(String mime) {
+        contentType = mime;
+        if (contentType != null) {
+            getResponse().setContentType(contentType);
+        }
+    }
+
+
+    @Override
+    public void addDateHeader(String name, long value) {
+        super.addDateHeader(name, value);
+        String lname = name.toLowerCase(Locale.ENGLISH);
+        if (lname.equals(LAST_MODIFIED)) {
+            lastModified = value;
+        }
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+        super.addHeader(name, value);
+        String lname = name.toLowerCase(Locale.ENGLISH);
+        if (lname.equals(LAST_MODIFIED)) {
+            try {
+                synchronized(RFC1123_FORMAT) {
+                    lastModified = RFC1123_FORMAT.parse(value).getTime();
+                }
+            } catch (Throwable ignore) {
+                ExceptionUtils.handleThrowable(ignore);
+            }
+        } else if (lname.equals(CONTENT_TYPE)) {
+            contentType = value;
+        }
+    }
+
+    @Override
+    public void setDateHeader(String name, long value) {
+        super.setDateHeader(name, value);
+        String lname = name.toLowerCase(Locale.ENGLISH);
+        if (lname.equals(LAST_MODIFIED)) {
+            lastModified = value;
+        }
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+        super.setHeader(name, value);
+        String lname = name.toLowerCase(Locale.ENGLISH);
+        if (lname.equals(LAST_MODIFIED)) {
+            try {
+                synchronized(RFC1123_FORMAT) {
+                    lastModified = RFC1123_FORMAT.parse(value).getTime();
+                }
+            } catch (Throwable ignore) {
+                ExceptionUtils.handleThrowable(ignore);
+            }
+        }
+        else if (lname.equals(CONTENT_TYPE))
+        {
+            contentType = value;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSICommand.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSICommand.java
new file mode 100644
index 0000000..bb56cf7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSICommand.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.PrintWriter;
+/**
+ * The interface that all SSI commands ( SSIEcho, SSIInclude, ...) must
+ * implement.
+ * 
+ * @author Bip Thelin
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSICommand.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public interface SSICommand {
+    /**
+     * Write the output of the command to the writer.
+     * 
+     * @param ssiMediator
+     *            the ssi mediator
+     * @param commandName
+     *            the name of the actual command ( ie. echo )
+     * @param paramNames
+     *            The parameter names
+     * @param paramValues
+     *            The parameter values
+     * @param writer
+     *            the writer to output to
+     * @return the most current modified date resulting from any SSI commands
+     * @throws SSIStopProcessingException
+     *             if SSI processing should be aborted
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer)
+            throws SSIStopProcessingException;
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConditional.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConditional.java
new file mode 100644
index 0000000..dce1ad4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConditional.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.PrintWriter;
+import java.text.ParseException;
+/**
+ * SSI command that handles all conditional directives.
+ * 
+ * @version $Revision: 1.1 $
+ * @author Paul Speed
+ * @author David Becker
+ */
+public class SSIConditional implements SSICommand {
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer)
+            throws SSIStopProcessingException {
+        // Assume anything using conditionals was modified by it
+        long lastModified = System.currentTimeMillis();
+        // Retrieve the current state information
+        SSIConditionalState state = ssiMediator.getConditionalState();
+        if ("if".equalsIgnoreCase(commandName)) {
+            // Do nothing if we are nested in a false branch
+            // except count it
+            if (state.processConditionalCommandsOnly) {
+                state.nestingCount++;
+                return lastModified;
+            }
+            state.nestingCount = 0;
+            // Evaluate the expression
+            if (evaluateArguments(paramNames, paramValues, ssiMediator)) {
+                // No more branches can be taken for this if block
+                state.branchTaken = true;
+            } else {
+                // Do not process this branch
+                state.processConditionalCommandsOnly = true;
+                state.branchTaken = false;
+            }
+        } else if ("elif".equalsIgnoreCase(commandName)) {
+            // No need to even execute if we are nested in
+            // a false branch
+            if (state.nestingCount > 0) return lastModified;
+            // If a branch was already taken in this if block
+            // then disable output and return
+            if (state.branchTaken) {
+                state.processConditionalCommandsOnly = true;
+                return lastModified;
+            }
+            // Evaluate the expression
+            if (evaluateArguments(paramNames, paramValues, ssiMediator)) {
+                // Turn back on output and mark the branch
+                state.processConditionalCommandsOnly = false;
+                state.branchTaken = true;
+            } else {
+                // Do not process this branch
+                state.processConditionalCommandsOnly = true;
+                state.branchTaken = false;
+            }
+        } else if ("else".equalsIgnoreCase(commandName)) {
+            // No need to even execute if we are nested in
+            // a false branch
+            if (state.nestingCount > 0) return lastModified;
+            // If we've already taken another branch then
+            // disable output otherwise enable it.
+            state.processConditionalCommandsOnly = state.branchTaken;
+            // And in any case, it's safe to say a branch
+            // has been taken.
+            state.branchTaken = true;
+        } else if ("endif".equalsIgnoreCase(commandName)) {
+            // If we are nested inside a false branch then pop out
+            // one level on the nesting count
+            if (state.nestingCount > 0) {
+                state.nestingCount--;
+                return lastModified;
+            }
+            // Turn output back on
+            state.processConditionalCommandsOnly = false;
+            // Reset the branch status for any outer if blocks,
+            // since clearly we took a branch to have gotten here
+            // in the first place.
+            state.branchTaken = true;
+        } else {
+            throw new SSIStopProcessingException();
+            //throw new SsiCommandException( "Not a conditional command:" +
+            // cmdName );
+        }
+        return lastModified;
+    }
+
+
+    /**
+     * Retrieves the expression from the specified arguments and peforms the
+     * necessary evaluation steps.
+     */
+    private boolean evaluateArguments(String[] names, String[] values,
+            SSIMediator ssiMediator) throws SSIStopProcessingException {
+        String expr = getExpression(names, values);
+        if (expr == null) {
+            throw new SSIStopProcessingException();
+            //throw new SsiCommandException( "No expression specified." );
+        }
+        try {
+            ExpressionParseTree tree = new ExpressionParseTree(expr,
+                    ssiMediator);
+            return tree.evaluateTree();
+        } catch (ParseException e) {
+            //throw new SsiCommandException( "Error parsing expression." );
+            throw new SSIStopProcessingException();
+        }
+    }
+
+
+    /**
+     * Returns the "expr" if the arg name is appropriate, otherwise returns
+     * null.
+     */
+    private String getExpression(String[] paramNames, String[] paramValues) {
+        if ("expr".equalsIgnoreCase(paramNames[0])) return paramValues[0];
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConditionalState.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConditionalState.java
new file mode 100644
index 0000000..6f9f6a4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConditionalState.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+/**
+ * This class is used by SSIMediator and SSIConditional to keep track of state
+ * information necessary to process the nested conditional commands ( if, elif,
+ * else, endif ).
+ * 
+ * @version $Revision: 1.1 $
+ * @author Dan Sandberg
+ * @author Paul Speed
+ */
+class SSIConditionalState {
+    /**
+     * Set to true if the current conditional has already been completed, i.e.:
+     * a branch was taken.
+     */
+    boolean branchTaken = false;
+    /**
+     * Counts the number of nested false branches.
+     */
+    int nestingCount = 0;
+    /**
+     * Set to true if only conditional commands ( if, elif, else, endif )
+     * should be processed.
+     */
+    boolean processConditionalCommandsOnly = false;
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConfig.java
new file mode 100644
index 0000000..2ee0b54
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIConfig.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.PrintWriter;
+/**
+ * Implements the Server-side #exec command
+ * 
+ * @author Bip Thelin
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIConfig.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public final class SSIConfig implements SSICommand {
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer) {
+        for (int i = 0; i < paramNames.length; i++) {
+            String paramName = paramNames[i];
+            String paramValue = paramValues[i];
+            String substitutedValue = ssiMediator
+                    .substituteVariables(paramValue);
+            if (paramName.equalsIgnoreCase("errmsg")) {
+                ssiMediator.setConfigErrMsg(substitutedValue);
+            } else if (paramName.equalsIgnoreCase("sizefmt")) {
+                ssiMediator.setConfigSizeFmt(substitutedValue);
+            } else if (paramName.equalsIgnoreCase("timefmt")) {
+                ssiMediator.setConfigTimeFmt(substitutedValue);
+            } else {
+                ssiMediator.log("#config--Invalid attribute: " + paramName);
+                //We need to fetch this value each time, since it may change
+                // during the
+                // loop
+                String configErrMsg = ssiMediator.getConfigErrMsg();
+                writer.write(configErrMsg);
+            }
+        }
+        // Setting config options doesn't really change the page
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIEcho.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIEcho.java
new file mode 100644
index 0000000..4946e53
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIEcho.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.PrintWriter;
+/**
+ * Return the result associated with the supplied Server Variable.
+ * 
+ * @author Bip Thelin
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIEcho.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIEcho implements SSICommand {
+    protected static final String DEFAULT_ENCODING = "entity";
+    protected static final String MISSING_VARIABLE_VALUE = "(none)";
+
+
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer) {
+        String encoding = DEFAULT_ENCODING;
+        String originalValue = null;
+        String errorMessage = ssiMediator.getConfigErrMsg();
+        for (int i = 0; i < paramNames.length; i++) {
+            String paramName = paramNames[i];
+            String paramValue = paramValues[i];
+            if (paramName.equalsIgnoreCase("var")) {
+                originalValue = paramValue;
+            } else if (paramName.equalsIgnoreCase("encoding")) {
+                if (isValidEncoding(paramValue)) {
+                    encoding = paramValue;
+                } else {
+                    ssiMediator.log("#echo--Invalid encoding: " + paramValue);
+                    writer.write(errorMessage);
+                }
+            } else {
+                ssiMediator.log("#echo--Invalid attribute: " + paramName);
+                writer.write(errorMessage);
+            }
+        }
+        String variableValue = ssiMediator.getVariableValue(
+                originalValue, encoding);
+        if (variableValue == null) {
+            variableValue = MISSING_VARIABLE_VALUE;
+        }
+        writer.write(variableValue);
+        return System.currentTimeMillis();
+    }
+
+
+    protected boolean isValidEncoding(String encoding) {
+        return encoding.equalsIgnoreCase("url")
+                || encoding.equalsIgnoreCase("entity")
+                || encoding.equalsIgnoreCase("none");
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIExec.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIExec.java
new file mode 100644
index 0000000..991b8a9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIExec.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+
+import org.apache.catalina.util.IOTools;
+/**
+ * Implements the Server-side #exec command
+ * 
+ * @author Bip Thelin
+ * @author Amy Roh
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIExec.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIExec implements SSICommand {
+    protected SSIInclude ssiInclude = new SSIInclude();
+    protected static final int BUFFER_SIZE = 1024;
+
+
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer) {
+        long lastModified = 0;
+        String configErrMsg = ssiMediator.getConfigErrMsg();
+        String paramName = paramNames[0];
+        String paramValue = paramValues[0];
+        String substitutedValue = ssiMediator.substituteVariables(paramValue);
+        if (paramName.equalsIgnoreCase("cgi")) {
+            lastModified = ssiInclude.process(ssiMediator, "include",
+                    new String[]{"virtual"}, new String[]{substitutedValue},
+                    writer);
+        } else if (paramName.equalsIgnoreCase("cmd")) {
+            boolean foundProgram = false;
+            try {
+                Runtime rt = Runtime.getRuntime();
+                Process proc = rt.exec(substitutedValue);
+                foundProgram = true;
+                BufferedReader stdOutReader = new BufferedReader(
+                        new InputStreamReader(proc.getInputStream()));
+                BufferedReader stdErrReader = new BufferedReader(
+                        new InputStreamReader(proc.getErrorStream()));
+                char[] buf = new char[BUFFER_SIZE];
+                IOTools.flow(stdErrReader, writer, buf);
+                IOTools.flow(stdOutReader, writer, buf);
+                proc.waitFor();
+                lastModified = System.currentTimeMillis();                
+            } catch (InterruptedException e) {
+                ssiMediator.log("Couldn't exec file: " + substitutedValue, e);
+                writer.write(configErrMsg);
+            } catch (IOException e) {
+                if (!foundProgram) {
+                    //apache doesn't output an error message if it can't find
+                    // a program
+                }
+                ssiMediator.log("Couldn't exec file: " + substitutedValue, e);
+            }
+        }
+        return lastModified;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIExternalResolver.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIExternalResolver.java
new file mode 100644
index 0000000..e688104
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIExternalResolver.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Date;
+/**
+ * Interface used by SSIMediator to talk to the 'outside world' ( usually a
+ * servlet )
+ * 
+ * @author Dan Sandberg
+ * @version $Id: SSIExternalResolver.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public interface SSIExternalResolver {
+    /**
+     * Adds any external variables to the variableNames collection.
+     * 
+     * @param variableNames
+     *            the collection to add to
+     */
+    public void addVariableNames(Collection<String> variableNames);
+
+
+    public String getVariableValue(String name);
+
+
+    /**
+     * Set the named variable to the specified value. If value is null, then
+     * the variable will be removed ( ie. a call to getVariableValue will
+     * return null )
+     * 
+     * @param name
+     *            of the variable
+     * @param value
+     *            of the variable
+     */
+    public void setVariableValue(String name, String value);
+
+
+    /**
+     * Returns the current date. This is useful for putting the SSI stuff in a
+     * regression test. Since you can make the current date a constant, it
+     * makes testing easier since the output won't change.
+     * 
+     * @return the data
+     */
+    public Date getCurrentDate();
+
+
+    public long getFileSize(String path, boolean virtual) throws IOException;
+
+
+    public long getFileLastModified(String path, boolean virtual)
+            throws IOException;
+
+
+    public String getFileText(String path, boolean virtual) throws IOException;
+
+
+    public void log(String message, Throwable throwable);
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFilter.java
new file mode 100644
index 0000000..cf15aa4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFilter.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Reader;
+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.apache.catalina.Globals;
+/**
+ * Filter to process SSI requests within a webpage. Mapped to a content types
+ * from within web.xml.
+ * 
+ * @author David Becker
+ * @version $Id: SSIFilter.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ * @see org.apache.catalina.ssi.SSIServlet
+ */
+public class SSIFilter implements Filter {
+    protected FilterConfig config = null;
+    /** Debug level for this servlet. */
+    protected int debug = 0;
+    /** Expiration time in seconds for the doc. */
+    protected Long expires = null;
+    /** virtual path can be webapp-relative */
+    protected boolean isVirtualWebappRelative = false;
+    /** regex pattern to match when evaluating content types */
+    protected Pattern contentTypeRegEx = null;
+    /** default pattern for ssi filter content type matching */
+    protected Pattern shtmlRegEx =
+        Pattern.compile("text/x-server-parsed-html(;.*)?");
+    /** Allow exec (normally blocked for security) */
+    protected boolean allowExec = false;
+
+
+    //----------------- Public methods.
+    /**
+     * Initialize this servlet.
+     * 
+     * @exception ServletException
+     *                if an error occurs
+     */
+    public void init(FilterConfig config) throws ServletException {
+        this.config = config;
+        
+        if (config.getInitParameter("debug") != null) {
+            debug = Integer.parseInt(config.getInitParameter("debug"));
+        }
+
+        if (config.getInitParameter("contentType") != null) {
+            contentTypeRegEx = Pattern.compile(config.getInitParameter("contentType"));
+        } else {
+            contentTypeRegEx = shtmlRegEx;
+        }
+
+        isVirtualWebappRelative = 
+            Boolean.parseBoolean(config.getInitParameter("isVirtualWebappRelative"));
+
+        if (config.getInitParameter("expires") != null)
+            expires = Long.valueOf(config.getInitParameter("expires"));
+
+        allowExec = Boolean.parseBoolean(config.getInitParameter("allowExec"));
+
+        if (debug > 0)
+            config.getServletContext().log(
+                    "SSIFilter.init() SSI invoker started with 'debug'=" + debug);
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        // cast once
+        HttpServletRequest req = (HttpServletRequest)request;
+        HttpServletResponse res = (HttpServletResponse)response;
+        
+        // indicate that we're in SSI processing
+        req.setAttribute(Globals.SSI_FLAG_ATTR, "true");           
+
+        // setup to capture output
+        ByteArrayServletOutputStream basos = new ByteArrayServletOutputStream();
+        ResponseIncludeWrapper responseIncludeWrapper =
+            new ResponseIncludeWrapper(config.getServletContext(),req, res, basos);
+
+        // process remainder of filter chain
+        chain.doFilter(req, responseIncludeWrapper);
+
+        // we can't assume the chain flushed its output
+        responseIncludeWrapper.flushOutputStreamOrWriter();
+        byte[] bytes = basos.toByteArray();
+
+        // get content type
+        String contentType = responseIncludeWrapper.getContentType();
+
+        // is this an allowed type for SSI processing?
+        if (contentTypeRegEx.matcher(contentType).matches()) {
+            String encoding = res.getCharacterEncoding();
+
+            // set up SSI processing 
+            SSIExternalResolver ssiExternalResolver =
+                new SSIServletExternalResolver(config.getServletContext(), req,
+                        res, isVirtualWebappRelative, debug, encoding);
+            SSIProcessor ssiProcessor = new SSIProcessor(ssiExternalResolver,
+                    debug, allowExec);
+            
+            // prepare readers/writers
+            Reader reader =
+                new InputStreamReader(new ByteArrayInputStream(bytes), encoding);
+            ByteArrayOutputStream ssiout = new ByteArrayOutputStream();
+            PrintWriter writer =
+                new PrintWriter(new OutputStreamWriter(ssiout, encoding));
+            
+            // do SSI processing  
+            long lastModified = ssiProcessor.process(reader,
+                    responseIncludeWrapper.getLastModified(), writer);
+            
+            // set output bytes
+            writer.flush();
+            bytes = ssiout.toByteArray();
+            
+            // override headers
+            if (expires != null) {
+                res.setDateHeader("expires", (new java.util.Date()).getTime()
+                        + expires.longValue() * 1000);
+            }
+            if (lastModified > 0) {
+                res.setDateHeader("last-modified", lastModified);
+            }
+            res.setContentLength(bytes.length);
+            
+            Matcher shtmlMatcher =
+                shtmlRegEx.matcher(responseIncludeWrapper.getContentType());
+            if (shtmlMatcher.matches()) {
+                // Convert shtml mime type to ordinary html mime type but preserve
+                // encoding, if any.
+                String enc = shtmlMatcher.group(1);
+                res.setContentType("text/html" + ((enc != null) ? enc : ""));
+            }
+        }
+
+        // write output
+        OutputStream out = null;
+        try {
+            out = res.getOutputStream();
+        } catch (IllegalStateException e) {
+            // Ignore, will try to use a writer
+        }
+        if (out == null) {
+            res.getWriter().write(new String(bytes));
+        } else {
+            out.write(bytes);
+        }
+    }
+
+    public void destroy() {
+        // NOOP
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFlastmod.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFlastmod.java
new file mode 100644
index 0000000..7f8bf61
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFlastmod.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+
+import org.apache.catalina.util.DateTool;
+import org.apache.catalina.util.Strftime;
+/**
+ * Implements the Server-side #flastmod command
+ * 
+ * @author Bip Thelin
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIFlastmod.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public final class SSIFlastmod implements SSICommand {
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer) {
+        long lastModified = 0;
+        String configErrMsg = ssiMediator.getConfigErrMsg();
+        for (int i = 0; i < paramNames.length; i++) {
+            String paramName = paramNames[i];
+            String paramValue = paramValues[i];
+            String substitutedValue = ssiMediator
+                    .substituteVariables(paramValue);
+            try {
+                if (paramName.equalsIgnoreCase("file")
+                        || paramName.equalsIgnoreCase("virtual")) {
+                    boolean virtual = paramName.equalsIgnoreCase("virtual");
+                    lastModified = ssiMediator.getFileLastModified(
+                            substitutedValue, virtual);
+                    Date date = new Date(lastModified);
+                    String configTimeFmt = ssiMediator.getConfigTimeFmt();
+                    writer.write(formatDate(date, configTimeFmt));
+                } else {
+                    ssiMediator.log("#flastmod--Invalid attribute: "
+                            + paramName);
+                    writer.write(configErrMsg);
+                }
+            } catch (IOException e) {
+                ssiMediator.log(
+                        "#flastmod--Couldn't get last modified for file: "
+                                + substitutedValue, e);
+                writer.write(configErrMsg);
+            }
+        }
+        return lastModified;
+    }
+
+
+    protected String formatDate(Date date, String configTimeFmt) {
+        Strftime strftime = new Strftime(configTimeFmt, DateTool.LOCALE_US);
+        return strftime.format(date);
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFsize.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFsize.java
new file mode 100644
index 0000000..4b1d18e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIFsize.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+/**
+ * Implements the Server-side #fsize command
+ * 
+ * @author Bip Thelin
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIFsize.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public final class SSIFsize implements SSICommand {
+    protected static final int ONE_KILOBYTE = 1024;
+    protected static final int ONE_MEGABYTE = 1024 * 1024;
+
+
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer) {
+        long lastModified = 0;
+        String configErrMsg = ssiMediator.getConfigErrMsg();
+        for (int i = 0; i < paramNames.length; i++) {
+            String paramName = paramNames[i];
+            String paramValue = paramValues[i];
+            String substitutedValue = ssiMediator
+                    .substituteVariables(paramValue);
+            try {
+                if (paramName.equalsIgnoreCase("file")
+                        || paramName.equalsIgnoreCase("virtual")) {
+                    boolean virtual = paramName.equalsIgnoreCase("virtual");
+                    lastModified = ssiMediator.getFileLastModified(
+                            substitutedValue, virtual);
+                    long size = ssiMediator.getFileSize(substitutedValue,
+                            virtual);
+                    String configSizeFmt = ssiMediator.getConfigSizeFmt();
+                    writer.write(formatSize(size, configSizeFmt));
+                } else {
+                    ssiMediator.log("#fsize--Invalid attribute: " + paramName);
+                    writer.write(configErrMsg);
+                }
+            } catch (IOException e) {
+                ssiMediator.log("#fsize--Couldn't get size for file: "
+                        + substitutedValue, e);
+                writer.write(configErrMsg);
+            }
+        }
+        return lastModified;
+    }
+
+
+    public String repeat(char aChar, int numChars) {
+        if (numChars < 0) {
+            throw new IllegalArgumentException("Num chars can't be negative");
+        }
+        StringBuilder buf = new StringBuilder();
+        for (int i = 0; i < numChars; i++) {
+            buf.append(aChar);
+        }
+        return buf.toString();
+    }
+
+
+    public String padLeft(String str, int maxChars) {
+        String result = str;
+        int charsToAdd = maxChars - str.length();
+        if (charsToAdd > 0) {
+            result = repeat(' ', charsToAdd) + str;
+        }
+        return result;
+    }
+
+
+    //We try to mimic Apache here, as we do everywhere
+    //All the 'magic' numbers are from the util_script.c Apache source file.
+    protected String formatSize(long size, String format) {
+        String retString = "";
+        if (format.equalsIgnoreCase("bytes")) {
+            DecimalFormat decimalFormat = new DecimalFormat("#,##0");
+            retString = decimalFormat.format(size);
+        } else {
+            if (size == 0) {
+                retString = "0k";
+            } else if (size < ONE_KILOBYTE) {
+                retString = "1k";
+            } else if (size < ONE_MEGABYTE) {
+                retString = Long.toString((size + 512) / ONE_KILOBYTE);
+                retString += "k";
+            } else if (size < 99 * ONE_MEGABYTE) {
+                DecimalFormat decimalFormat = new DecimalFormat("0.0M");
+                retString = decimalFormat.format(size / (double)ONE_MEGABYTE);
+            } else {
+                retString = Long.toString((size + (529 * ONE_KILOBYTE))
+                        / ONE_MEGABYTE);
+                retString += "M";
+            }
+            retString = padLeft(retString, 5);
+        }
+        return retString;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIInclude.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIInclude.java
new file mode 100644
index 0000000..5e4e6ae
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIInclude.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+/**
+ * Implements the Server-side #include command
+ * 
+ * @author Bip Thelin
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIInclude.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public final class SSIInclude implements SSICommand {
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer) {
+        long lastModified = 0;
+        String configErrMsg = ssiMediator.getConfigErrMsg();
+        for (int i = 0; i < paramNames.length; i++) {
+            String paramName = paramNames[i];
+            String paramValue = paramValues[i];
+            String substitutedValue = ssiMediator
+                    .substituteVariables(paramValue);
+            try {
+                if (paramName.equalsIgnoreCase("file")
+                        || paramName.equalsIgnoreCase("virtual")) {
+                    boolean virtual = paramName.equalsIgnoreCase("virtual");
+                    lastModified = ssiMediator.getFileLastModified(
+                            substitutedValue, virtual);
+                    String text = ssiMediator.getFileText(substitutedValue,
+                            virtual);
+                    writer.write(text);
+                } else {
+                    ssiMediator.log("#include--Invalid attribute: "
+                            + paramName);
+                    writer.write(configErrMsg);
+                }
+            } catch (IOException e) {
+                ssiMediator.log("#include--Couldn't include file: "
+                        + substitutedValue, e);
+                writer.write(configErrMsg);
+            }
+        }
+        return lastModified;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIMediator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIMediator.java
new file mode 100644
index 0000000..02e1498
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIMediator.java
@@ -0,0 +1,354 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TimeZone;
+
+import org.apache.catalina.util.DateTool;
+import org.apache.catalina.util.Strftime;
+import org.apache.catalina.util.URLEncoder;
+import org.apache.tomcat.util.http.HttpMessages;
+/**
+ * Allows the different SSICommand implementations to share data/talk to each
+ * other
+ * 
+ * @author Bip Thelin
+ * @author Amy Roh
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIMediator.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIMediator {
+    protected static final String DEFAULT_CONFIG_ERR_MSG = "[an error occurred while processing this directive]";
+    protected static final String DEFAULT_CONFIG_TIME_FMT = "%A, %d-%b-%Y %T %Z";
+    protected static final String DEFAULT_CONFIG_SIZE_FMT = "abbrev";
+    protected static URLEncoder urlEncoder;
+    protected String configErrMsg = DEFAULT_CONFIG_ERR_MSG;
+    protected String configTimeFmt = DEFAULT_CONFIG_TIME_FMT;
+    protected String configSizeFmt = DEFAULT_CONFIG_SIZE_FMT;
+    protected String className = getClass().getName();
+    protected SSIExternalResolver ssiExternalResolver;
+    protected long lastModifiedDate;
+    protected int debug;
+    protected Strftime strftime;
+    protected SSIConditionalState conditionalState = new SSIConditionalState();
+    static {
+        //We try to encode only the same characters that apache does
+        urlEncoder = new URLEncoder();
+        urlEncoder.addSafeCharacter(',');
+        urlEncoder.addSafeCharacter(':');
+        urlEncoder.addSafeCharacter('-');
+        urlEncoder.addSafeCharacter('_');
+        urlEncoder.addSafeCharacter('.');
+        urlEncoder.addSafeCharacter('*');
+        urlEncoder.addSafeCharacter('/');
+        urlEncoder.addSafeCharacter('!');
+        urlEncoder.addSafeCharacter('~');
+        urlEncoder.addSafeCharacter('\'');
+        urlEncoder.addSafeCharacter('(');
+        urlEncoder.addSafeCharacter(')');
+    }
+
+
+    public SSIMediator(SSIExternalResolver ssiExternalResolver,
+            long lastModifiedDate, int debug) {
+        this.ssiExternalResolver = ssiExternalResolver;
+        this.lastModifiedDate = lastModifiedDate;
+        this.debug = debug;
+        setConfigTimeFmt(DEFAULT_CONFIG_TIME_FMT, true);
+    }
+
+
+    public void setConfigErrMsg(String configErrMsg) {
+        this.configErrMsg = configErrMsg;
+    }
+
+
+    public void setConfigTimeFmt(String configTimeFmt) {
+        setConfigTimeFmt(configTimeFmt, false);
+    }
+
+
+    public void setConfigTimeFmt(String configTimeFmt, boolean fromConstructor) {
+        this.configTimeFmt = configTimeFmt;
+        //What's the story here with DateTool.LOCALE_US?? Why??
+        this.strftime = new Strftime(configTimeFmt, DateTool.LOCALE_US);
+        //Variables like DATE_LOCAL, DATE_GMT, and LAST_MODIFIED need to be
+        // updated when
+        //the timefmt changes. This is what Apache SSI does.
+        setDateVariables(fromConstructor);
+    }
+
+
+    public void setConfigSizeFmt(String configSizeFmt) {
+        this.configSizeFmt = configSizeFmt;
+    }
+
+
+    public String getConfigErrMsg() {
+        return configErrMsg;
+    }
+
+
+    public String getConfigTimeFmt() {
+        return configTimeFmt;
+    }
+
+
+    public String getConfigSizeFmt() {
+        return configSizeFmt;
+    }
+
+
+    public SSIConditionalState getConditionalState() {
+        return conditionalState;
+    }
+
+
+    public Collection<String> getVariableNames() {
+        Set<String> variableNames = new HashSet<String>();
+        //These built-in variables are supplied by the mediator ( if not
+        // over-written by
+        // the user ) and always exist
+        variableNames.add("DATE_GMT");
+        variableNames.add("DATE_LOCAL");
+        variableNames.add("LAST_MODIFIED");
+        ssiExternalResolver.addVariableNames(variableNames);
+        //Remove any variables that are reserved by this class
+        Iterator<String> iter = variableNames.iterator();
+        while (iter.hasNext()) {
+            String name = iter.next();
+            if (isNameReserved(name)) {
+                iter.remove();
+            }
+        }
+        return variableNames;
+    }
+
+
+    public long getFileSize(String path, boolean virtual) throws IOException {
+        return ssiExternalResolver.getFileSize(path, virtual);
+    }
+
+
+    public long getFileLastModified(String path, boolean virtual)
+            throws IOException {
+        return ssiExternalResolver.getFileLastModified(path, virtual);
+    }
+
+
+    public String getFileText(String path, boolean virtual) throws IOException {
+        return ssiExternalResolver.getFileText(path, virtual);
+    }
+
+
+    protected boolean isNameReserved(String name) {
+        return name.startsWith(className + ".");
+    }
+
+
+    public String getVariableValue(String variableName) {
+        return getVariableValue(variableName, "none");
+    }
+
+
+    public void setVariableValue(String variableName, String variableValue) {
+        if (!isNameReserved(variableName)) {
+            ssiExternalResolver.setVariableValue(variableName, variableValue);
+        }
+    }
+
+
+    public String getVariableValue(String variableName, String encoding) {
+        String lowerCaseVariableName = variableName.toLowerCase(Locale.ENGLISH);
+        String variableValue = null;
+        if (!isNameReserved(lowerCaseVariableName)) {
+            //Try getting it externally first, if it fails, try getting the
+            // 'built-in'
+            // value
+            variableValue = ssiExternalResolver.getVariableValue(variableName);
+            if (variableValue == null) {
+                variableName = variableName.toUpperCase(Locale.ENGLISH);
+                variableValue = ssiExternalResolver
+                        .getVariableValue(className + "." + variableName);
+            }
+            if (variableValue != null) {
+                variableValue = encode(variableValue, encoding);
+            }
+        }
+        return variableValue;
+    }
+
+
+    /**
+     * Applies variable substitution to the specified String and returns the
+     * new resolved string.
+     */
+    public String substituteVariables(String val) {
+        // If it has no references or HTML entities then no work
+        // need to be done
+        if (val.indexOf('$') < 0 && val.indexOf('&') < 0) return val;
+        
+        // HTML decoding
+        val = val.replace("&lt;", "<");
+        val = val.replace("&gt;", ">");
+        val = val.replace("&quot;", "\"");
+        val = val.replace("&amp;", "&");
+
+        StringBuilder sb = new StringBuilder(val);
+        int charStart = sb.indexOf("&#");
+        while (charStart > -1) {
+            int charEnd = sb.indexOf(";", charStart);
+            if (charEnd > -1) {
+                char c = (char) Integer.parseInt(
+                        sb.substring(charStart + 2, charEnd));
+                sb.delete(charStart, charEnd + 1);
+                sb.insert(charStart, c);
+                charStart = sb.indexOf("&#");
+            } else {
+                break;
+            }
+        }
+
+        for (int i = 0; i < sb.length();) {
+            // Find the next $
+            for (; i < sb.length(); i++) {
+                if (sb.charAt(i) == '$') {
+                    i++;
+                    break;
+                }
+            }
+            if (i == sb.length()) break;
+            // Check to see if the $ is escaped
+            if (i > 1 && sb.charAt(i - 2) == '\\') {
+                sb.deleteCharAt(i - 2);
+                i--;
+                continue;
+            }
+            int nameStart = i;
+            int start = i - 1;
+            int end = -1;
+            int nameEnd = -1;
+            char endChar = ' ';
+            // Check for {} wrapped var
+            if (sb.charAt(i) == '{') {
+                nameStart++;
+                endChar = '}';
+            }
+            // Find the end of the var reference
+            for (; i < sb.length(); i++) {
+                if (sb.charAt(i) == endChar) break;
+            }
+            end = i;
+            nameEnd = end;
+            if (endChar == '}') end++;
+            // We should now have enough to extract the var name
+            String varName = sb.substring(nameStart, nameEnd);
+            String value = getVariableValue(varName);
+            if (value == null) value = "";
+            // Replace the var name with its value
+            sb.replace(start, end, value);
+            // Start searching for the next $ after the value
+            // that was just substituted.
+            i = start + value.length();
+        }
+        return sb.toString();
+    }
+
+
+    protected String formatDate(Date date, TimeZone timeZone) {
+        String retVal;
+        if (timeZone != null) {
+            //we temporarily change strftime. Since SSIMediator is inherently
+            // single-threaded, this
+            //isn't a problem
+            TimeZone oldTimeZone = strftime.getTimeZone();
+            strftime.setTimeZone(timeZone);
+            retVal = strftime.format(date);
+            strftime.setTimeZone(oldTimeZone);
+        } else {
+            retVal = strftime.format(date);
+        }
+        return retVal;
+    }
+
+
+    protected String encode(String value, String encoding) {
+        String retVal = null;
+        if (encoding.equalsIgnoreCase("url")) {
+            retVal = urlEncoder.encode(value);
+        } else if (encoding.equalsIgnoreCase("none")) {
+            retVal = value;
+        } else if (encoding.equalsIgnoreCase("entity")) {
+            retVal = HttpMessages.filter(value);
+        } else {
+            //This shouldn't be possible
+            throw new IllegalArgumentException("Unknown encoding: " + encoding);
+        }
+        return retVal;
+    }
+
+
+    public void log(String message) {
+        ssiExternalResolver.log(message, null);
+    }
+
+
+    public void log(String message, Throwable throwable) {
+        ssiExternalResolver.log(message, throwable);
+    }
+
+
+    protected void setDateVariables(boolean fromConstructor) {
+        boolean alreadySet = ssiExternalResolver.getVariableValue(className
+                + ".alreadyset") != null;
+        //skip this if we are being called from the constructor, and this has
+        // already
+        // been set
+        if (!(fromConstructor && alreadySet)) {
+            ssiExternalResolver.setVariableValue(className + ".alreadyset",
+                    "true");
+            Date date = new Date();
+            TimeZone timeZone = TimeZone.getTimeZone("GMT");
+            String retVal = formatDate(date, timeZone);
+            //If we are setting on of the date variables, we want to remove
+            // them from the
+            // user
+            //defined list of variables, because this is what Apache does
+            setVariableValue("DATE_GMT", null);
+            ssiExternalResolver.setVariableValue(className + ".DATE_GMT",
+                    retVal);
+            retVal = formatDate(date, null);
+            setVariableValue("DATE_LOCAL", null);
+            ssiExternalResolver.setVariableValue(className + ".DATE_LOCAL",
+                    retVal);
+            retVal = formatDate(new Date(lastModifiedDate), null);
+            setVariableValue("LAST_MODIFIED", null);
+            ssiExternalResolver.setVariableValue(className + ".LAST_MODIFIED",
+                    retVal);
+        }
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIPrintenv.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIPrintenv.java
new file mode 100644
index 0000000..9e632af
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIPrintenv.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Iterator;
+/**
+ * Implements the Server-side #printenv command
+ * 
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIPrintenv.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIPrintenv implements SSICommand {
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer) {
+        long lastModified = 0;
+        //any arguments should produce an error
+        if (paramNames.length > 0) {
+            String errorMessage = ssiMediator.getConfigErrMsg();
+            writer.write(errorMessage);
+        } else {
+            Collection<String> variableNames = ssiMediator.getVariableNames();
+            Iterator<String> iter = variableNames.iterator();
+            while (iter.hasNext()) {
+                String variableName = iter.next();
+                String variableValue = ssiMediator
+                        .getVariableValue(variableName);
+                //This shouldn't happen, since all the variable names must
+                // have values
+                if (variableValue == null) {
+                    variableValue = "(none)";
+                }
+                writer.write(variableName);
+                writer.write('=');
+                writer.write(variableValue);
+                writer.write('\n');
+                lastModified = System.currentTimeMillis();
+            }
+        }
+        return lastModified;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIProcessor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIProcessor.java
new file mode 100644
index 0000000..5316952
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIProcessor.java
@@ -0,0 +1,332 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+import org.apache.catalina.util.IOTools;
+/**
+ * The entry point to SSI processing. This class does the actual parsing,
+ * delegating to the SSIMediator, SSICommand, and SSIExternalResolver as
+ * necessary[
+ * 
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIProcessor.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIProcessor {
+    /** The start pattern */
+    protected static final String COMMAND_START = "<!--#";
+    /** The end pattern */
+    protected static final String COMMAND_END = "-->";
+    protected static final int BUFFER_SIZE = 4096;
+    protected SSIExternalResolver ssiExternalResolver;
+    protected HashMap<String,SSICommand> commands =
+        new HashMap<String,SSICommand>();
+    protected int debug;
+    protected final boolean allowExec;
+
+
+    public SSIProcessor(SSIExternalResolver ssiExternalResolver, int debug,
+            boolean allowExec) {
+        this.ssiExternalResolver = ssiExternalResolver;
+        this.debug = debug;
+        this.allowExec = allowExec;
+        addBuiltinCommands();
+    }
+
+
+    protected void addBuiltinCommands() {
+        addCommand("config", new SSIConfig());
+        addCommand("echo", new SSIEcho());
+        if (allowExec) {
+            addCommand("exec", new SSIExec());
+        }
+        addCommand("include", new SSIInclude());
+        addCommand("flastmod", new SSIFlastmod());
+        addCommand("fsize", new SSIFsize());
+        addCommand("printenv", new SSIPrintenv());
+        addCommand("set", new SSISet());
+        SSIConditional ssiConditional = new SSIConditional();
+        addCommand("if", ssiConditional);
+        addCommand("elif", ssiConditional);
+        addCommand("endif", ssiConditional);
+        addCommand("else", ssiConditional);
+    }
+
+
+    public void addCommand(String name, SSICommand command) {
+        commands.put(name, command);
+    }
+
+
+    /**
+     * Process a file with server-side commands, reading from reader and
+     * writing the processed version to writer. NOTE: We really should be doing
+     * this in a streaming way rather than converting it to an array first.
+     * 
+     * @param reader
+     *            the reader to read the file containing SSIs from
+     * @param writer
+     *            the writer to write the file with the SSIs processed.
+     * @return the most current modified date resulting from any SSI commands
+     * @throws IOException
+     *             when things go horribly awry. Should be unlikely since the
+     *             SSICommand usually catches 'normal' IOExceptions.
+     */
+    public long process(Reader reader, long lastModifiedDate,
+            PrintWriter writer) throws IOException {
+        SSIMediator ssiMediator = new SSIMediator(ssiExternalResolver,
+                lastModifiedDate, debug);
+        StringWriter stringWriter = new StringWriter();
+        IOTools.flow(reader, stringWriter);
+        String fileContents = stringWriter.toString();
+        stringWriter = null;
+        int index = 0;
+        boolean inside = false;
+        StringBuilder command = new StringBuilder();
+        try {
+            while (index < fileContents.length()) {
+                char c = fileContents.charAt(index);
+                if (!inside) {
+                    if (c == COMMAND_START.charAt(0)
+                            && charCmp(fileContents, index, COMMAND_START)) {
+                        inside = true;
+                        index += COMMAND_START.length();
+                        command.setLength(0); //clear the command string
+                    } else {
+                        if (!ssiMediator.getConditionalState().processConditionalCommandsOnly) {
+                            writer.write(c);
+                        }
+                        index++;
+                    }
+                } else {
+                    if (c == COMMAND_END.charAt(0)
+                            && charCmp(fileContents, index, COMMAND_END)) {
+                        inside = false;
+                        index += COMMAND_END.length();
+                        String strCmd = parseCmd(command);
+                        if (debug > 0) {
+                            ssiExternalResolver.log(
+                                    "SSIProcessor.process -- processing command: "
+                                            + strCmd, null);
+                        }
+                        String[] paramNames = parseParamNames(command, strCmd
+                                .length());
+                        String[] paramValues = parseParamValues(command,
+                                strCmd.length(), paramNames.length);
+                        //We need to fetch this value each time, since it may
+                        // change
+                        // during the loop
+                        String configErrMsg = ssiMediator.getConfigErrMsg();
+                        SSICommand ssiCommand =
+                            commands.get(strCmd.toLowerCase(Locale.ENGLISH));
+                        String errorMessage = null;
+                        if (ssiCommand == null) {
+                            errorMessage = "Unknown command: " + strCmd;
+                        } else if (paramValues == null) {
+                            errorMessage = "Error parsing directive parameters.";
+                        } else if (paramNames.length != paramValues.length) {
+                            errorMessage = "Parameter names count does not match parameter values count on command: "
+                                    + strCmd;
+                        } else {
+                            // don't process the command if we are processing
+                            // conditional
+                            // commands only and the
+                            // command is not conditional
+                            if (!ssiMediator.getConditionalState().processConditionalCommandsOnly
+                                    || ssiCommand instanceof SSIConditional) {
+                                long lmd = ssiCommand.process(ssiMediator, strCmd,
+                                               paramNames, paramValues, writer);
+                                if (lmd > lastModifiedDate) {
+                                    lastModifiedDate = lmd;
+                                }                                    
+                            }
+                        }
+                        if (errorMessage != null) {
+                            ssiExternalResolver.log(errorMessage, null);
+                            writer.write(configErrMsg);
+                        }
+                    } else {
+                        command.append(c);
+                        index++;
+                    }
+                }
+            }
+        } catch (SSIStopProcessingException e) {
+            //If we are here, then we have already stopped processing, so all
+            // is good
+        }
+        return lastModifiedDate;
+    }
+
+
+    /**
+     * Parse a StringBuilder and take out the param type token. Called from
+     * <code>requestHandler</code>
+     * 
+     * @param cmd
+     *            a value of type 'StringBuilder'
+     * @return a value of type 'String[]'
+     */
+    protected String[] parseParamNames(StringBuilder cmd, int start) {
+        int bIdx = start;
+        int i = 0;
+        int quotes = 0;
+        boolean inside = false;
+        StringBuilder retBuf = new StringBuilder();
+        while (bIdx < cmd.length()) {
+            if (!inside) {
+                while (bIdx < cmd.length() && isSpace(cmd.charAt(bIdx)))
+                    bIdx++;
+                if (bIdx >= cmd.length()) break;
+                inside = !inside;
+            } else {
+                while (bIdx < cmd.length() && cmd.charAt(bIdx) != '=') {
+                    retBuf.append(cmd.charAt(bIdx));
+                    bIdx++;
+                }
+                retBuf.append('=');
+                inside = !inside;
+                quotes = 0;
+                boolean escaped = false;
+                for (; bIdx < cmd.length() && quotes != 2; bIdx++) {
+                    char c = cmd.charAt(bIdx);
+                    // Need to skip escaped characters
+                    if (c == '\\' && !escaped) {
+                        escaped = true;
+                        continue;
+                    }
+                    if (c == '"' && !escaped) quotes++;
+                    escaped = false;
+                }
+            }
+        }
+        StringTokenizer str = new StringTokenizer(retBuf.toString(), "=");
+        String[] retString = new String[str.countTokens()];
+        while (str.hasMoreTokens()) {
+            retString[i++] = str.nextToken().trim();
+        }
+        return retString;
+    }
+
+
+    /**
+     * Parse a StringBuilder and take out the param token. Called from
+     * <code>requestHandler</code>
+     * 
+     * @param cmd
+     *            a value of type 'StringBuilder'
+     * @return a value of type 'String[]'
+     */
+    protected String[] parseParamValues(StringBuilder cmd, int start, int count) {
+        int valIndex = 0;
+        boolean inside = false;
+        String[] vals = new String[count];
+        StringBuilder sb = new StringBuilder();
+        char endQuote = 0;
+        for (int bIdx = start; bIdx < cmd.length(); bIdx++) {
+            if (!inside) {
+                while (bIdx < cmd.length() && !isQuote(cmd.charAt(bIdx)))
+                    bIdx++;
+                if (bIdx >= cmd.length()) break;
+                inside = !inside;
+                endQuote = cmd.charAt(bIdx);
+            } else {
+                boolean escaped = false;
+                for (; bIdx < cmd.length(); bIdx++) {
+                    char c = cmd.charAt(bIdx);
+                    // Check for escapes
+                    if (c == '\\' && !escaped) {
+                        escaped = true;
+                        continue;
+                    }
+                    // If we reach the other " then stop
+                    if (c == endQuote && !escaped) break;
+                    // Since parsing of attributes and var
+                    // substitution is done in separate places,
+                    // we need to leave escape in the string
+                    if (c == '$' && escaped) sb.append('\\');
+                    escaped = false;
+                    sb.append(c);
+                }
+                // If we hit the end without seeing a quote
+                // the signal an error
+                if (bIdx == cmd.length()) return null;
+                vals[valIndex++] = sb.toString();
+                sb.delete(0, sb.length()); // clear the buffer
+                inside = !inside;
+            }
+        }
+        return vals;
+    }
+
+
+    /**
+     * Parse a StringBuilder and take out the command token. Called from
+     * <code>requestHandler</code>
+     * 
+     * @param cmd
+     *            a value of type 'StringBuilder'
+     * @return a value of type 'String', or null if there is none
+     */
+    private String parseCmd(StringBuilder cmd) {
+        int firstLetter = -1;
+        int lastLetter = -1;
+        for (int i = 0; i < cmd.length(); i++) {
+            char c = cmd.charAt(i);
+            if (Character.isLetter(c)) {
+                if (firstLetter == -1) {
+                    firstLetter = i;
+                }
+                lastLetter = i;
+            } else if (isSpace(c)) {
+                if (lastLetter > -1) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        String command = null;
+        if (firstLetter != -1) {
+            command = cmd.substring(firstLetter, lastLetter + 1);
+        }
+        return command;
+    }
+
+
+    protected boolean charCmp(String buf, int index, String command) {
+        return buf.regionMatches(index, command, 0, command.length());
+    }
+
+
+    protected boolean isSpace(char c) {
+        return c == ' ' || c == '\n' || c == '\t' || c == '\r';
+    }
+    
+    protected boolean isQuote(char c) {
+        return c == '\'' || c == '\"' || c == '`';
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServlet.java
new file mode 100644
index 0000000..ed36cff
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServlet.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Locale;
+
+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.apache.catalina.Globals;
+/**
+ * Servlet to process SSI requests within a webpage. Mapped to a path from
+ * within web.xml.
+ * 
+ * @author Bip Thelin
+ * @author Amy Roh
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIServlet.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIServlet extends HttpServlet {
+    private static final long serialVersionUID = 1L;
+
+    /** Debug level for this servlet. */
+    protected int debug = 0;
+    /** Should the output be buffered. */
+    protected boolean buffered = false;
+    /** Expiration time in seconds for the doc. */
+    protected Long expires = null;
+    /** virtual path can be webapp-relative */
+    protected boolean isVirtualWebappRelative = false;
+    /** Input encoding. If not specified, uses platform default */
+    protected String inputEncoding = null;
+    /** Output encoding. If not specified, uses platform default */
+    protected String outputEncoding = "UTF-8";
+    /** Allow exec (normally blocked for security) */
+    protected boolean allowExec = false;
+
+
+    //----------------- Public methods.
+    /**
+     * Initialize this servlet.
+     * 
+     * @exception ServletException
+     *                if an error occurs
+     */
+    @Override
+    public void init() throws ServletException {
+        
+        if (getServletConfig().getInitParameter("debug") != null)
+            debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
+        
+        isVirtualWebappRelative = 
+            Boolean.parseBoolean(getServletConfig().getInitParameter("isVirtualWebappRelative"));
+        
+        if (getServletConfig().getInitParameter("expires") != null)
+            expires = Long.valueOf(getServletConfig().getInitParameter("expires"));
+        
+        buffered = Boolean.parseBoolean(getServletConfig().getInitParameter("buffered"));
+        
+        inputEncoding = getServletConfig().getInitParameter("inputEncoding");
+        
+        if (getServletConfig().getInitParameter("outputEncoding") != null)
+            outputEncoding = getServletConfig().getInitParameter("outputEncoding");
+        
+        allowExec = Boolean.parseBoolean(
+                getServletConfig().getInitParameter("allowExec"));
+
+        if (debug > 0)
+            log("SSIServlet.init() SSI invoker started with 'debug'=" + debug);
+
+    }
+
+
+    /**
+     * Process and forward the GET request to our <code>requestHandler()</code>*
+     * 
+     * @param req
+     *            a value of type 'HttpServletRequest'
+     * @param res
+     *            a value of type 'HttpServletResponse'
+     * @exception IOException
+     *                if an error occurs
+     * @exception ServletException
+     *                if an error occurs
+     */
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse res)
+            throws IOException, ServletException {
+        if (debug > 0) log("SSIServlet.doGet()");
+        requestHandler(req, res);
+    }
+
+
+    /**
+     * Process and forward the POST request to our
+     * <code>requestHandler()</code>.
+     * 
+     * @param req
+     *            a value of type 'HttpServletRequest'
+     * @param res
+     *            a value of type 'HttpServletResponse'
+     * @exception IOException
+     *                if an error occurs
+     * @exception ServletException
+     *                if an error occurs
+     */
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse res)
+            throws IOException, ServletException {
+        if (debug > 0) log("SSIServlet.doPost()");
+        requestHandler(req, res);
+    }
+
+
+    /**
+     * Process our request and locate right SSI command.
+     * 
+     * @param req
+     *            a value of type 'HttpServletRequest'
+     * @param res
+     *            a value of type 'HttpServletResponse'
+     */
+    protected void requestHandler(HttpServletRequest req,
+            HttpServletResponse res) throws IOException {
+        ServletContext servletContext = getServletContext();
+        String path = SSIServletRequestUtil.getRelativePath(req);
+        if (debug > 0)
+            log("SSIServlet.requestHandler()\n" + "Serving "
+                    + (buffered?"buffered ":"unbuffered ") + "resource '"
+                    + path + "'");
+        // Exclude any resource in the /WEB-INF and /META-INF subdirectories
+        // (the "toUpperCase()" avoids problems on Windows systems)
+        if (path == null || path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF")
+                || path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF")) {
+            res.sendError(HttpServletResponse.SC_NOT_FOUND, path);
+            log("Can't serve file: " + path);
+            return;
+        }
+        URL resource = servletContext.getResource(path);
+        if (resource == null) {
+            res.sendError(HttpServletResponse.SC_NOT_FOUND, path);
+            log("Can't find file: " + path);
+            return;
+        }
+        String resourceMimeType = servletContext.getMimeType(path);
+        if (resourceMimeType == null) {
+            resourceMimeType = "text/html";
+        }
+        res.setContentType(resourceMimeType + ";charset=" + outputEncoding);
+        if (expires != null) {
+            res.setDateHeader("Expires", (new java.util.Date()).getTime()
+                    + expires.longValue() * 1000);
+        }
+        req.setAttribute(Globals.SSI_FLAG_ATTR, "true");
+        processSSI(req, res, resource);
+    }
+
+
+    protected void processSSI(HttpServletRequest req, HttpServletResponse res,
+            URL resource) throws IOException {
+        SSIExternalResolver ssiExternalResolver =
+            new SSIServletExternalResolver(getServletContext(), req, res,
+                    isVirtualWebappRelative, debug, inputEncoding);
+        SSIProcessor ssiProcessor = new SSIProcessor(ssiExternalResolver,
+                debug, allowExec);
+        PrintWriter printWriter = null;
+        StringWriter stringWriter = null;
+        if (buffered) {
+            stringWriter = new StringWriter();
+            printWriter = new PrintWriter(stringWriter);
+        } else {
+            printWriter = res.getWriter();
+        }
+
+        URLConnection resourceInfo = resource.openConnection();
+        InputStream resourceInputStream = resourceInfo.getInputStream();
+        String encoding = resourceInfo.getContentEncoding();
+        if (encoding == null) {
+            encoding = inputEncoding;
+        }
+        InputStreamReader isr;
+        if (encoding == null) {
+            isr = new InputStreamReader(resourceInputStream);
+        } else {
+            isr = new InputStreamReader(resourceInputStream, encoding);
+        }
+        BufferedReader bufferedReader = new BufferedReader(isr);
+
+        long lastModified = ssiProcessor.process(bufferedReader,
+                resourceInfo.getLastModified(), printWriter);
+        if (lastModified > 0) {
+            res.setDateHeader("last-modified", lastModified);
+        }
+        if (buffered) {
+            printWriter.flush();
+            String text = stringWriter.toString();
+            res.getWriter().write(text);
+        }
+        bufferedReader.close();
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServletExternalResolver.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServletExternalResolver.java
new file mode 100644
index 0000000..af470f2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServletExternalResolver.java
@@ -0,0 +1,584 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Locale;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.coyote.Constants;
+
+/**
+ * An implementation of SSIExternalResolver that is used with servlets.
+ * 
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSIServletExternalResolver.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIServletExternalResolver implements SSIExternalResolver {
+    protected final String VARIABLE_NAMES[] = {"AUTH_TYPE", "CONTENT_LENGTH",
+            "CONTENT_TYPE", "DOCUMENT_NAME", "DOCUMENT_URI",
+            "GATEWAY_INTERFACE", "HTTP_ACCEPT", "HTTP_ACCEPT_ENCODING",
+            "HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION", "HTTP_HOST",
+            "HTTP_REFERER", "HTTP_USER_AGENT", "PATH_INFO", "PATH_TRANSLATED",
+            "QUERY_STRING", "QUERY_STRING_UNESCAPED", "REMOTE_ADDR",
+            "REMOTE_HOST", "REMOTE_PORT", "REMOTE_USER", "REQUEST_METHOD",
+            "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_ADDR",
+            "SERVER_NAME", "SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SOFTWARE",
+            "UNIQUE_ID"};
+    protected ServletContext context;
+    protected HttpServletRequest req;
+    protected HttpServletResponse res;
+    protected boolean isVirtualWebappRelative;
+    protected int debug;
+    protected String inputEncoding;
+
+    public SSIServletExternalResolver(ServletContext context,
+            HttpServletRequest req, HttpServletResponse res,
+            boolean isVirtualWebappRelative, int debug, String inputEncoding) {
+        this.context = context;
+        this.req = req;
+        this.res = res;
+        this.isVirtualWebappRelative = isVirtualWebappRelative;
+        this.debug = debug;
+        this.inputEncoding = inputEncoding;
+    }
+
+
+    @Override
+    public void log(String message, Throwable throwable) {
+        //We can't assume that Servlet.log( message, null )
+        //is the same as Servlet.log( message ), since API
+        //doesn't seem to say so.
+        if (throwable != null) {
+            context.log(message, throwable);
+        } else {
+            context.log(message);
+        }
+    }
+
+
+    @Override
+    public void addVariableNames(Collection<String> variableNames) {
+        for (int i = 0; i < VARIABLE_NAMES.length; i++) {
+            String variableName = VARIABLE_NAMES[i];
+            String variableValue = getVariableValue(variableName);
+            if (variableValue != null) {
+                variableNames.add(variableName);
+            }
+        }
+        Enumeration<String> e = req.getAttributeNames();
+        while (e.hasMoreElements()) {
+            String name = e.nextElement();
+            if (!isNameReserved(name)) {
+                variableNames.add(name);
+            }
+        }
+    }
+
+
+    protected Object getReqAttributeIgnoreCase(String targetName) {
+        Object object = null;
+        if (!isNameReserved(targetName)) {
+            object = req.getAttribute(targetName);
+            if (object == null) {
+                Enumeration<String> e = req.getAttributeNames();
+                while (e.hasMoreElements()) {
+                    String name = e.nextElement();
+                    if (targetName.equalsIgnoreCase(name)
+                            && !isNameReserved(name)) {
+                        object = req.getAttribute(name);
+                        if (object != null) {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return object;
+    }
+
+
+    protected boolean isNameReserved(String name) {
+        return name.startsWith("java.") || name.startsWith("javax.")
+                || name.startsWith("sun.");
+    }
+
+
+    @Override
+    public void setVariableValue(String name, String value) {
+        if (!isNameReserved(name)) {
+            req.setAttribute(name, value);
+        }
+    }
+
+
+    @Override
+    public String getVariableValue(String name) {
+        String retVal = null;
+        Object object = getReqAttributeIgnoreCase(name);
+        if (object != null) {
+            retVal = object.toString();
+        } else {
+            retVal = getCGIVariable(name);
+        }
+        return retVal;
+    }
+
+
+    protected String getCGIVariable(String name) {
+        String retVal = null;
+        String[] nameParts = name.toUpperCase(Locale.ENGLISH).split("_");
+        int requiredParts = 2;
+        if (nameParts.length == 1) {
+            if (nameParts[0].equals("PATH")) {
+                requiredParts = 1;
+            }
+        }
+        else if (nameParts[0].equals("AUTH")) {
+            if (nameParts[1].equals("TYPE")) {
+                retVal = req.getAuthType();
+            }
+        } else if(nameParts[0].equals("CONTENT")) {
+            if (nameParts[1].equals("LENGTH")) {
+                int contentLength = req.getContentLength();
+                if (contentLength >= 0) {
+                    retVal = Integer.toString(contentLength);
+                }
+            } else if (nameParts[1].equals("TYPE")) {
+                retVal = req.getContentType();
+            }
+        } else if (nameParts[0].equals("DOCUMENT")) {
+            if (nameParts[1].equals("NAME")) {
+                String requestURI = req.getRequestURI();
+                retVal = requestURI.substring(requestURI.lastIndexOf('/') + 1);
+            } else if (nameParts[1].equals("URI")) {
+                retVal = req.getRequestURI();
+            }
+        } else if (name.equalsIgnoreCase("GATEWAY_INTERFACE")) {
+            retVal = "CGI/1.1";
+        } else if (nameParts[0].equals("HTTP")) {
+            if (nameParts[1].equals("ACCEPT")) {
+                String accept = null;
+                if (nameParts.length == 2) {
+                    accept = "Accept";
+                } else if (nameParts[2].equals("ENCODING")) {
+                    requiredParts = 3;
+                    accept = "Accept-Encoding";
+                } else if (nameParts[2].equals("LANGUAGE")) {
+                    requiredParts = 3;
+                    accept = "Accept-Language";
+                }
+                if (accept != null) {
+                    Enumeration<String> acceptHeaders = req.getHeaders(accept);
+                    if (acceptHeaders != null)
+                        if (acceptHeaders.hasMoreElements()) {
+                            StringBuilder rv = new StringBuilder(
+                                    acceptHeaders.nextElement());
+                            while (acceptHeaders.hasMoreElements()) {
+                                rv.append(", ");
+                                rv.append(acceptHeaders.nextElement());
+                            }
+                        retVal = rv.toString();
+                    }
+                }
+            }
+            else if (nameParts[1].equals("CONNECTION")) {
+                retVal = req.getHeader("Connection");
+            }
+            else if (nameParts[1].equals("HOST")) {
+                retVal = req.getHeader("Host");
+            }
+            else if (nameParts[1].equals("REFERER")) {
+                retVal = req.getHeader("Referer");
+            }
+            else if (nameParts[1].equals("USER"))
+                if (nameParts.length == 3)
+                    if (nameParts[2].equals("AGENT")) {
+                        requiredParts = 3;
+                        retVal = req.getHeader("User-Agent");
+                    }
+
+        } else if (nameParts[0].equals("PATH")) {
+            if (nameParts[1].equals("INFO")) {
+                retVal = req.getPathInfo();
+            } else if (nameParts[1].equals("TRANSLATED")) {
+                retVal = req.getPathTranslated();
+            }
+        } else if (nameParts[0].equals("QUERY")) {
+            if (nameParts[1].equals("STRING")) {
+                String queryString = req.getQueryString();
+                if (nameParts.length == 2) {
+                    //apache displays this as an empty string rather than (none)
+                    retVal = nullToEmptyString(queryString);
+                } else if (nameParts[2].equals("UNESCAPED")) {
+                    requiredParts = 3;
+                    if (queryString != null) {
+                        // Use default as a last resort
+                        String queryStringEncoding =
+                            Constants.DEFAULT_CHARACTER_ENCODING;
+                
+                        String uriEncoding = null;
+                        boolean useBodyEncodingForURI = false;
+                
+                        // Get encoding settings from request / connector if
+                        // possible
+                        String requestEncoding = req.getCharacterEncoding();
+                        if (req instanceof Request) {
+                            uriEncoding =
+                                ((Request)req).getConnector().getURIEncoding();
+                            useBodyEncodingForURI = ((Request)req)
+                                    .getConnector().getUseBodyEncodingForURI();
+                        }
+                
+                        // If valid, apply settings from request / connector
+                        if (uriEncoding != null) {
+                            queryStringEncoding = uriEncoding;
+                        } else if(useBodyEncodingForURI) {
+                            if (requestEncoding != null) {
+                                queryStringEncoding = requestEncoding;
+                            }
+                        }
+                
+                        try {
+                            retVal = URLDecoder.decode(queryString,
+                                    queryStringEncoding);                       
+                        } catch (UnsupportedEncodingException e) {
+                            retVal = queryString;
+                        }
+                    }
+                }
+            }
+        } else if(nameParts[0].equals("REMOTE")) {
+            if (nameParts[1].equals("ADDR")) {
+                retVal = req.getRemoteAddr();
+            } else if (nameParts[1].equals("HOST")) {
+                retVal = req.getRemoteHost();
+            } else if (nameParts[1].equals("IDENT")) {
+                // Not implemented
+            } else if (nameParts[1].equals("PORT")) {
+                retVal = Integer.toString( req.getRemotePort());
+            } else if (nameParts[1].equals("USER")) {
+                retVal = req.getRemoteUser();
+            }
+        } else if(nameParts[0].equals("REQUEST")) {
+            if (nameParts[1].equals("METHOD")) {
+                retVal = req.getMethod();
+            }
+            else if (nameParts[1].equals("URI")) {
+                // If this is an error page, get the original URI
+                retVal = (String) req.getAttribute(
+                        RequestDispatcher.FORWARD_REQUEST_URI);
+                if (retVal == null) retVal=req.getRequestURI();
+            }
+        } else if (nameParts[0].equals("SCRIPT")) {
+            String scriptName = req.getServletPath();
+            if (nameParts[1].equals("FILENAME")) {
+                retVal = context.getRealPath(scriptName);
+            }
+            else if (nameParts[1].equals("NAME")) {
+                retVal = scriptName;
+            }
+        } else if (nameParts[0].equals("SERVER")) {
+            if (nameParts[1].equals("ADDR")) {
+                retVal = req.getLocalAddr();
+            }
+            if (nameParts[1].equals("NAME")) {
+                retVal = req.getServerName();
+            } else if (nameParts[1].equals("PORT")) {
+                retVal = Integer.toString(req.getServerPort());
+            } else if (nameParts[1].equals("PROTOCOL")) {
+                retVal = req.getProtocol();
+            } else if (nameParts[1].equals("SOFTWARE")) {
+                StringBuilder rv = new StringBuilder(context.getServerInfo());
+                rv.append(" ");
+                rv.append(System.getProperty("java.vm.name"));
+                rv.append("/");
+                rv.append(System.getProperty("java.vm.version"));
+                rv.append(" ");
+                rv.append(System.getProperty("os.name"));
+                retVal = rv.toString();
+            }
+        } else if (name.equalsIgnoreCase("UNIQUE_ID")) {
+            retVal = req.getRequestedSessionId();
+        }
+        if (requiredParts != nameParts.length) return null;
+            return retVal;
+    }
+
+    @Override
+    public Date getCurrentDate() {
+        return new Date();
+    }
+
+
+    protected String nullToEmptyString(String string) {
+        String retVal = string;
+        if (retVal == null) {
+            retVal = "";
+        }
+        return retVal;
+    }
+
+
+    protected String getPathWithoutFileName(String servletPath) {
+        String retVal = null;
+        int lastSlash = servletPath.lastIndexOf('/');
+        if (lastSlash >= 0) {
+            //cut off file name
+            retVal = servletPath.substring(0, lastSlash + 1);
+        }
+        return retVal;
+    }
+
+
+    protected String getPathWithoutContext(final String contextPath,
+            final String servletPath) {
+        if (servletPath.startsWith(contextPath)) {
+            return servletPath.substring(contextPath.length());
+        }
+        return servletPath;
+    }
+
+
+    protected String getAbsolutePath(String path) throws IOException {
+        String pathWithoutContext = SSIServletRequestUtil.getRelativePath(req);
+        String prefix = getPathWithoutFileName(pathWithoutContext);
+        if (prefix == null) {
+            throw new IOException("Couldn't remove filename from path: "
+                    + pathWithoutContext);
+        }
+        String fullPath = prefix + path;
+        String retVal = RequestUtil.normalize(fullPath);
+        if (retVal == null) {
+            throw new IOException("Normalization yielded null on path: "
+                    + fullPath);
+        }
+        return retVal;
+    }
+
+
+    protected ServletContextAndPath getServletContextAndPathFromNonVirtualPath(
+            String nonVirtualPath) throws IOException {
+        if (nonVirtualPath.startsWith("/") || nonVirtualPath.startsWith("\\")) {
+            throw new IOException("A non-virtual path can't be absolute: "
+                    + nonVirtualPath);
+        }
+        if (nonVirtualPath.indexOf("../") >= 0) {
+            throw new IOException("A non-virtual path can't contain '../' : "
+                    + nonVirtualPath);
+        }
+        String path = getAbsolutePath(nonVirtualPath);
+        ServletContextAndPath csAndP = new ServletContextAndPath(
+                context, path);
+        return csAndP;
+    }
+
+
+    protected ServletContextAndPath getServletContextAndPathFromVirtualPath(
+            String virtualPath) throws IOException {
+
+        if (!virtualPath.startsWith("/") && !virtualPath.startsWith("\\")) {
+            return new ServletContextAndPath(context,
+                    getAbsolutePath(virtualPath));
+        }
+
+        String normalized = RequestUtil.normalize(virtualPath);
+        if (isVirtualWebappRelative) {
+            return new ServletContextAndPath(context, normalized);
+        }
+
+        ServletContext normContext = context.getContext(normalized);
+        if (normContext == null) {
+            throw new IOException("Couldn't get context for path: "
+                    + normalized);
+        }
+        //If it's the root context, then there is no context element
+        // to remove,
+        // ie:
+        // '/file1.shtml' vs '/appName1/file1.shtml'
+        if (!isRootContext(normContext)) {
+            String noContext = getPathWithoutContext(
+                    normContext.getContextPath(), normalized);
+            if (noContext == null) {
+                throw new IOException(
+                        "Couldn't remove context from path: "
+                                + normalized);
+            }
+            return new ServletContextAndPath(normContext, noContext);
+        }
+
+        return new ServletContextAndPath(normContext, normalized);
+    }
+
+
+    //Assumes servletContext is not-null
+    //Assumes that identity comparison will be true for the same context
+    //Assuming the above, getContext("/") will be non-null as long as the root
+    // context is
+    // accessible.
+    //If it isn't, then servletContext can't be the root context anyway, hence
+    // they will
+    // not match.
+    protected boolean isRootContext(ServletContext servletContext) {
+        return servletContext == servletContext.getContext("/");
+    }
+
+
+    protected ServletContextAndPath getServletContextAndPath(
+            String originalPath, boolean virtual) throws IOException {
+        ServletContextAndPath csAndP = null;
+        if (debug > 0) {
+            log("SSIServletExternalResolver.getServletContextAndPath( "
+                    + originalPath + ", " + virtual + ")", null);
+        }
+        if (virtual) {
+            csAndP = getServletContextAndPathFromVirtualPath(originalPath);
+        } else {
+            csAndP = getServletContextAndPathFromNonVirtualPath(originalPath);
+        }
+        return csAndP;
+    }
+
+
+    protected URLConnection getURLConnection(String originalPath,
+            boolean virtual) throws IOException {
+        ServletContextAndPath csAndP = getServletContextAndPath(originalPath,
+                virtual);
+        ServletContext context = csAndP.getServletContext();
+        String path = csAndP.getPath();
+        URL url = context.getResource(path);
+        if (url == null) {
+            throw new IOException("Context did not contain resource: " + path);
+        }
+        URLConnection urlConnection = url.openConnection();
+        return urlConnection;
+    }
+
+
+    @Override
+    public long getFileLastModified(String path, boolean virtual)
+            throws IOException {
+        long lastModified = 0;
+        try {
+            URLConnection urlConnection = getURLConnection(path, virtual);
+            lastModified = urlConnection.getLastModified();
+        } catch (IOException e) {
+            // Ignore this. It will always fail for non-file based includes
+        }
+        return lastModified;
+    }
+
+
+    @Override
+    public long getFileSize(String path, boolean virtual) throws IOException {
+        long fileSize = -1;
+        try {
+            URLConnection urlConnection = getURLConnection(path, virtual);
+            fileSize = urlConnection.getContentLength();
+        } catch (IOException e) {
+            // Ignore this. It will always fail for non-file based includes
+        }
+        return fileSize;
+    }
+
+
+    //We are making lots of unnecessary copies of the included data here. If
+    //someone ever complains that this is slow, we should connect the included
+    // stream to the print writer that SSICommand uses.
+    @Override
+    public String getFileText(String originalPath, boolean virtual)
+            throws IOException {
+        try {
+            ServletContextAndPath csAndP = getServletContextAndPath(
+                    originalPath, virtual);
+            ServletContext context = csAndP.getServletContext();
+            String path = csAndP.getPath();
+            RequestDispatcher rd = context.getRequestDispatcher(path);
+            if (rd == null) {
+                throw new IOException(
+                        "Couldn't get request dispatcher for path: " + path);
+            }
+            ByteArrayServletOutputStream basos =
+                new ByteArrayServletOutputStream();
+            ResponseIncludeWrapper responseIncludeWrapper =
+                new ResponseIncludeWrapper(context, req, res, basos);
+            rd.include(req, responseIncludeWrapper);
+            //We can't assume the included servlet flushed its output
+            responseIncludeWrapper.flushOutputStreamOrWriter();
+            byte[] bytes = basos.toByteArray();
+
+            //Assume platform default encoding unless otherwise specified
+            String retVal;
+            if (inputEncoding == null) {
+                retVal = new String( bytes );
+            } else {
+                retVal = new String (bytes, inputEncoding);
+            }
+
+            //make an assumption that an empty response is a failure. This is
+            // a problem
+            // if a truly empty file
+            //were included, but not sure how else to tell.
+            if (retVal.equals("") && !req.getMethod().equalsIgnoreCase(
+                    org.apache.coyote.http11.Constants.HEAD)) {
+                throw new IOException("Couldn't find file: " + path);
+            }
+            return retVal;
+        } catch (ServletException e) {
+            throw new IOException("Couldn't include file: " + originalPath
+                    + " because of ServletException: " + e.getMessage());
+        }
+    }
+
+    protected static class ServletContextAndPath {
+        protected ServletContext servletContext;
+        protected String path;
+
+
+        public ServletContextAndPath(ServletContext servletContext,
+                                     String path) {
+            this.servletContext = servletContext;
+            this.path = path;
+        }
+
+
+        public ServletContext getServletContext() {
+            return servletContext;
+        }
+
+
+        public String getPath() {
+            return path;
+        }
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServletRequestUtil.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServletRequestUtil.java
new file mode 100644
index 0000000..8f76401
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIServletRequestUtil.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.catalina.util.RequestUtil;
+public class SSIServletRequestUtil {
+    /**
+     * Return the relative path associated with this servlet. Taken from
+     * DefaultServlet.java. Perhaps this should be put in
+     * org.apache.catalina.util somewhere? Seems like it would be widely used.
+     * 
+     * @param request
+     *            The servlet request we are processing
+     */
+    public static String getRelativePath(HttpServletRequest request) {
+        // Are we being processed by a RequestDispatcher.include()?
+        if (request.getAttribute(
+                RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
+            String result = (String)request.getAttribute(
+                    RequestDispatcher.INCLUDE_PATH_INFO);
+            if (result == null)
+                result = (String)request.getAttribute(
+                        RequestDispatcher.INCLUDE_SERVLET_PATH);
+            if ((result == null) || (result.equals(""))) result = "/";
+            return (result);
+        }
+        // No, extract the desired path directly from the request
+        String result = request.getPathInfo();
+        if (result == null) {
+            result = request.getServletPath();
+        }
+        if ((result == null) || (result.equals(""))) {
+            result = "/";
+        }
+        return RequestUtil.normalize(result);
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSISet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSISet.java
new file mode 100644
index 0000000..73be6b1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSISet.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+import java.io.PrintWriter;
+/**
+ * Implements the Server-side #set command
+ * 
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @author David Becker
+ * @version $Id: SSISet.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSISet implements SSICommand {
+    /**
+     * @see SSICommand
+     */
+    public long process(SSIMediator ssiMediator, String commandName,
+            String[] paramNames, String[] paramValues, PrintWriter writer)
+            throws SSIStopProcessingException {
+        long lastModified = 0;
+        String errorMessage = ssiMediator.getConfigErrMsg();
+        String variableName = null;
+        for (int i = 0; i < paramNames.length; i++) {
+            String paramName = paramNames[i];
+            String paramValue = paramValues[i];
+            if (paramName.equalsIgnoreCase("var")) {
+                variableName = paramValue;
+            } else if (paramName.equalsIgnoreCase("value")) {
+                if (variableName != null) {
+                    String substitutedValue = ssiMediator
+                            .substituteVariables(paramValue);
+                    ssiMediator.setVariableValue(variableName,
+                            substitutedValue);
+                    lastModified = System.currentTimeMillis();
+                } else {
+                    ssiMediator.log("#set--no variable specified");
+                    writer.write(errorMessage);
+                    throw new SSIStopProcessingException();
+                }
+            } else {
+                ssiMediator.log("#set--Invalid attribute: " + paramName);
+                writer.write(errorMessage);
+                throw new SSIStopProcessingException();
+            }
+        }
+        return lastModified;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIStopProcessingException.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIStopProcessingException.java
new file mode 100644
index 0000000..edc3fd4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/SSIStopProcessingException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+
+/**
+ * Exception used to tell SSIProcessor that it should stop processing SSI
+ * commands. This is used to mimic the Apache behavior in #set with invalid
+ * attributes.
+ * 
+ * @author Paul Speed
+ * @author Dan Sandberg
+ * @version $Id: SSIStopProcessingException.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class SSIStopProcessingException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+    // No specific functionality for this class
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/package.html
new file mode 100644
index 0000000..61ed14a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ssi/package.html
@@ -0,0 +1,32 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<body>
+<p>This package contains code that is used by the SsiInvoker.</p>
+<p>This class consists of <code>SsiMediator.java</code> which works as a
+mediator between the different SsiCommands. To add a command you have to
+implement the <code>SsiCommand</code> interface and extend the
+<code>SsiMediator</code>. Commands currently implemented are</p>
+
+<ul>
+<li><b>SsiConfig</b> - Implementation of the NCSA command Config i.e. &lt;!--#config errmsg="error?"--&gt;</li>
+<li><b>SsiEcho</b> - Implementation of the NCSA command Echo i.e. &lt;!--#echo var="SERVER_NAME"--&gt;</li>
+<li><b>SsiExec</b> - Not implemented</li>
+<li><b>SsiFlastMod</b> - Implementation of the NCSA command flastmod i.e. &lt;!--#flastmod virtual="file"--&gt;</li>
+<li><b>SsiFsize</b> - Implementation of the NCSA command fsize i.e. &lt;!--#fsize file="file"--&gt;</li>
+<li><b>SsiInclude</b> - Implementation of the NCSA command Include i.e. &lt;!--#config virtual="includefile"--&gt;</li>
+</ul>
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ByteMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ByteMessage.java
new file mode 100644
index 0000000..677c91e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ByteMessage.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+/**
+ * A byte message is not serialized and deserialized by the channel
+ * instead it is sent as a byte array<br>
+ * By default Tribes uses java serialization when it receives an object
+ * to be sent over the wire. Java serialization is not the most
+ * efficient of serializing data, and Tribes might not even
+ * have access to the correct class loaders to deserialize the object properly.
+ * <br>
+ * The ByteMessage class is a class where the channel when it receives it will
+ * not attempt to perform serialization, instead it will simply stream the <code>getMessage()</code>
+ * bytes.<br>
+ * If you are using multiple applications on top of Tribes you should add some sort of header
+ * so that you can decide with the <code>ChannelListener.accept()</code> whether this message was intended
+ * for you.
+ * @author Filip Hanik
+ * @version $Id: ByteMessage.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class ByteMessage implements Externalizable {
+    /**
+     * Storage for the message to be sent
+     */
+    private byte[] message;
+
+
+    /**
+     * Creates an empty byte message
+     * Constructor also for deserialization
+     */
+    public ByteMessage() {
+    }
+
+    /**
+     * Creates a byte message wit h
+     * @param data byte[] - the message contents
+     */
+    public ByteMessage(byte[] data) {
+        message = data;
+    }
+
+    /**
+     * Returns the message contents of this byte message
+     * @return byte[] - message contents, can be null
+     */
+    public byte[] getMessage() {
+        return message;
+    }
+
+    /**
+     * Sets the message contents of this byte message
+     * @param message byte[]
+     */
+    public void setMessage(byte[] message) {
+        this.message = message;
+    }
+
+    /**
+     * @see java.io.Externalizable#readExternal
+     * @param in ObjectInput
+     * @throws IOException
+     */
+    @Override
+    public void readExternal(ObjectInput in ) throws IOException {
+        int length = in.readInt();
+        message = new byte[length];
+        in.readFully(message);
+    }
+
+    /**
+     * @see java.io.Externalizable#writeExternal
+     * @param out ObjectOutput
+     * @throws IOException
+     */
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeInt(message!=null?message.length:0);
+        if ( message!=null ) out.write(message,0,message.length);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Channel.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Channel.java
new file mode 100644
index 0000000..1354875
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Channel.java
@@ -0,0 +1,362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+
+/**
+ * Channel interface<br>
+ * A channel is a representation of a group of nodes all participating in some sort of
+ * communication with each other.<br>
+ * The channel is the main API class for Tribes, this is essentially the only class
+ * that an application needs to be aware of. Through the channel the application can:<br>
+ * 1. send messages<br>
+ * 2. receive message (by registering a <code>ChannelListener</code><br>
+ * 3. get all members of the group <code>getMembers()</code><br>
+ * 4. receive notifications of members added and members disappeared by
+ *    registering a <code>MembershipListener</code><br>
+ * <br>
+ * The channel has 5 major components:<br>
+ * 1. Data receiver, with a built in thread pool to receive messages from other peers<br>
+ * 2. Data sender, an implementation for sending data using NIO or java.io<br>
+ * 3. Membership listener,listens for membership broadcasts<br>
+ * 4. Membership broadcaster, broadcasts membership pings.<br>
+ * 5. Channel interceptors, the ability to manipulate messages as they are sent or arrive<br><br>
+ * The channel layout is:
+ * <pre><code>
+ *  ChannelListener_1..ChannelListener_N MembershipListener_1..MembershipListener_N [Application Layer]
+ *            \          \                  /                   /
+ *             \          \                /                   /
+ *              \          \              /                   /
+ *               \          \            /                   /
+ *                \          \          /                   /
+ *                 \          \        /                   /
+ *                  ---------------------------------------
+ *                                  |
+ *                                  |
+ *                               Channel
+ *                                  |
+ *                         ChannelInterceptor_1
+ *                                  |                                               [Channel stack]
+ *                         ChannelInterceptor_N
+ *                                  |
+ *                             Coordinator (implements MessageListener,MembershipListener,ChannelInterceptor)
+ *                          --------------------
+ *                         /        |           \
+ *                        /         |            \
+ *                       /          |             \
+ *                      /           |              \
+ *                     /            |               \
+ *           MembershipService ChannelSender ChannelReceiver                        [IO layer]
+ * </code></pre>
+ *
+ * For example usage @see org.apache.catalina.tribes.group.GroupChannel
+ * @author Filip Hanik
+ * @version $Id: Channel.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+public interface Channel {
+
+    /**
+     * Start and stop sequences can be controlled by these constants
+     * This allows you to start separate components of the channel <br>
+     * DEFAULT - starts or stops all components in the channel
+     * @see #start(int)
+     * @see #stop(int)
+     */
+    public static final int DEFAULT = 15;
+
+    /**
+     * Start and stop sequences can be controlled by these constants
+     * This allows you to start separate components of the channel <br>
+     * SND_RX_SEQ - starts or stops the data receiver. Start means opening a server socket
+     * in case of a TCP implementation
+     * @see #start(int)
+     * @see #stop(int)
+     */
+    public static final int SND_RX_SEQ = 1;
+
+    /**
+     * Start and stop sequences can be controlled by these constants
+     * This allows you to start separate components of the channel <br>
+     * SND_TX_SEQ - starts or stops the data sender. This should not open any sockets,
+     * as sockets are opened on demand when a message is being sent
+     * @see #start(int)
+     * @see #stop(int)
+     */
+    public static final int SND_TX_SEQ = 2;
+
+    /**
+     * Start and stop sequences can be controlled by these constants
+     * This allows you to start separate components of the channel <br>
+     * MBR_RX_SEQ - starts or stops the membership listener. In a multicast implementation
+     * this will open a datagram socket and join a group and listen for membership messages
+     * members joining
+     * @see #start(int)
+     * @see #stop(int)
+     */
+    public static final int MBR_RX_SEQ = 4;
+
+    /**
+     * Start and stop sequences can be controlled by these constants
+     * This allows you to start separate components of the channel <br>
+     * MBR_TX_SEQ - starts or stops the membership broadcaster. In a multicast implementation
+     * this will open a datagram socket and join a group and broadcast the local member information
+     * @see #start(int)
+     * @see #stop(int)
+     */
+    public static final int MBR_TX_SEQ = 8;
+
+    /**
+     * Send options, when a message is sent, it can have an option flag
+     * to trigger certain behavior. Most flags are used to trigger channel interceptors
+     * as the message passes through the channel stack. <br>
+     * However, there are five default flags that every channel implementation must implement<br>
+     * SEND_OPTIONS_BYTE_MESSAGE - The message is a pure byte message and no marshaling or unmarshaling will
+     * be performed.<br>
+     *
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_BYTE_MESSAGE = 0x0001;
+
+    /**
+     * Send options, when a message is sent, it can have an option flag
+     * to trigger certain behavior. Most flags are used to trigger channel interceptors
+     * as the message passes through the channel stack. <br>
+     * However, there are five default flags that every channel implementation must implement<br>
+     * SEND_OPTIONS_USE_ACK - Message is sent and an ACK is received when the message has been received by the recipient<br>
+     * If no ack is received, the message is not considered successful<br>
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_USE_ACK = 0x0002;
+
+    /**
+     * Send options, when a message is sent, it can have an option flag
+     * to trigger certain behavior. Most flags are used to trigger channel interceptors
+     * as the message passes through the channel stack. <br>
+     * However, there are five default flags that every channel implementation must implement<br>
+     * SEND_OPTIONS_SYNCHRONIZED_ACK - Message is sent and an ACK is received when the message has been received and
+     * processed by the recipient<br>
+     * If no ack is received, the message is not considered successful<br>
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_SYNCHRONIZED_ACK = 0x0004;
+
+    /**
+     * Send options, when a message is sent, it can have an option flag
+     * to trigger certain behavior. Most flags are used to trigger channel interceptors
+     * as the message passes through the channel stack. <br>
+     * However, there are five default flags that every channel implementation must implement<br>
+     * SEND_OPTIONS_ASYNCHRONOUS - Message is sent and an ACK is received when the message has been received and
+     * processed by the recipient<br>
+     * If no ack is received, the message is not considered successful<br>
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_ASYNCHRONOUS = 0x0008;
+
+    /**
+     * Send options, when a message is sent, it can have an option flag
+     * to trigger certain behavior. Most flags are used to trigger channel interceptors
+     * as the message passes through the channel stack. <br>
+     * However, there are five default flags that every channel implementation must implement<br>
+     * SEND_OPTIONS_SECURE - Message is sent over an encrypted channel<br>
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_SECURE = 0x0010;
+
+    /**
+     * Send options. When a message is sent with this flag on
+     * the system sends the message using UDP instead of TCP
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_UDP =  0x0020;
+
+    /**
+     * Send options. When a message is sent with this flag on
+     * the system sends a UDP message on the Multicast address instead of UDP or TCP to individual addresses
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_MULTICAST =  0x0040;
+
+    /**
+     * Send options, when a message is sent, it can have an option flag
+     * to trigger certain behavior. Most flags are used to trigger channel interceptors
+     * as the message passes through the channel stack. <br>
+     * However, there are five default flags that every channel implementation must implement<br>
+     * SEND_OPTIONS_DEFAULT - the default sending options, just a helper variable. <br>
+     * The default is <code>int SEND_OPTIONS_DEFAULT = SEND_OPTIONS_USE_ACK;</code><br>
+     * @see #SEND_OPTIONS_USE_ACK
+     * @see #send(Member[], Serializable , int)
+     * @see #send(Member[], Serializable, int, ErrorHandler)
+     */
+    public static final int SEND_OPTIONS_DEFAULT = SEND_OPTIONS_USE_ACK;
+
+
+    /**
+     * Adds an interceptor to the channel message chain.
+     * @param interceptor ChannelInterceptor
+     */
+    public void addInterceptor(ChannelInterceptor interceptor);
+
+    /**
+     * Starts up the channel. This can be called multiple times for individual services to start
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will start all services <BR>
+     * MBR_RX_SEQ - starts the membership receiver <BR>
+     * MBR_TX_SEQ - starts the membership broadcaster <BR>
+     * SND_TX_SEQ - starts the replication transmitter<BR>
+     * SND_RX_SEQ - starts the replication receiver<BR>
+     * <b>Note:</b> In order for the membership broadcaster to
+     * transmit the correct information, it has to be started after the replication receiver.
+     * @throws ChannelException if a startup error occurs or the service is already started or an error occurs.
+     */
+    public void start(int svc) throws ChannelException;
+
+    /**
+     * Shuts down the channel. This can be called multiple times for individual services to shutdown
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will shutdown all services <BR>
+     * MBR_RX_SEQ - stops the membership receiver <BR>
+     * MBR_TX_SEQ - stops the membership broadcaster <BR>
+     * SND_TX_SEQ - stops the replication transmitter<BR>
+     * SND_RX_SEQ - stops the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already stopped or an error occurs.
+     */
+    public void stop(int svc) throws ChannelException;
+
+    /**
+     * Send a message to one or more members in the cluster
+     * @param destination Member[] - the destinations, can not be null or zero length, the reason for that
+     * is that a membership change can occur and at that time the application is uncertain what group the message
+     * actually got sent to.
+     * @param msg Serializable - the message to send, has to be serializable, or a <code>ByteMessage</code> to
+     * send a pure byte array
+     * @param options int - sender options, see class documentation for each interceptor that is configured in order to trigger interceptors
+     * @return a unique Id that identifies the message that is sent
+     * @see ByteMessage
+     * @see #SEND_OPTIONS_USE_ACK
+     * @see #SEND_OPTIONS_ASYNCHRONOUS
+     * @see #SEND_OPTIONS_SYNCHRONIZED_ACK
+     */
+    public UniqueId send(Member[] destination, Serializable msg, int options) throws ChannelException;
+
+    /**
+     * Send a message to one or more members in the cluster
+     * @param destination Member[] - the destinations, null or zero length means all
+     * @param msg ClusterMessage - the message to send
+     * @param options int - sender options, see class documentation
+     * @param handler ErrorHandler - handle errors through a callback, rather than throw it
+     * @return a unique Id that identifies the message that is sent
+     * @exception ChannelException - if a serialization error happens.
+     */
+    public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler) throws ChannelException;
+
+    /**
+     * Sends a heart beat through the interceptor stacks
+     * Use this method to alert interceptors and other components to
+     * clean up garbage, timed out messages etc.<br>
+     * If you application has a background thread, then you can save one thread,
+     * by configuring your channel to not use an internal heartbeat thread
+     * and invoking this method.
+     * @see #setHeartbeat(boolean)
+     */
+    public void heartbeat();
+
+    /**
+     * Enables or disables internal heartbeat.
+     * @param enable boolean - default value is implementation specific
+     * @see #heartbeat()
+     */
+    public void setHeartbeat(boolean enable);
+
+    /**
+     * Add a membership listener, will get notified when a new member joins, leaves or crashes
+     * <br>If the membership listener implements the Heartbeat interface
+     * the <code>heartbeat()</code> method will be invoked when the heartbeat runs on the channel
+     * @param listener MembershipListener
+     * @see MembershipListener
+     */
+    public void addMembershipListener(MembershipListener listener);
+
+    /**
+     * Add a channel listener, this is a callback object when messages are received
+     * <br>If the channel listener implements the Heartbeat interface
+     * the <code>heartbeat()</code> method will be invoked when the heartbeat runs on the channel
+     * @param listener ChannelListener
+     * @see ChannelListener
+     * @see Heartbeat
+     */
+    public void addChannelListener(ChannelListener listener);
+
+    /**
+     * remove a membership listener, listeners are removed based on Object.hashCode and Object.equals
+     * @param listener MembershipListener
+     * @see MembershipListener
+     */
+    public void removeMembershipListener(MembershipListener listener);
+    /**
+     * remove a channel listener, listeners are removed based on Object.hashCode and Object.equals
+     * @param listener ChannelListener
+     * @see ChannelListener
+     */
+    public void removeChannelListener(ChannelListener listener);
+
+    /**
+     * Returns true if there are any members in the group,
+     * this call is the same as <code>getMembers().length>0</code>
+     * @return boolean - true if there are any members automatically discovered
+     */
+    public boolean hasMembers() ;
+
+    /**
+     * Get all current group members
+     * @return all members or empty array, never null
+     */
+    public Member[] getMembers() ;
+
+    /**
+     * Return the member that represents this node. This is also the data
+     * that gets broadcasted through the membership broadcaster component
+     * @param incAlive - optimization, true if you want it to calculate alive time
+     * since the membership service started.
+     * @return Member
+     */
+    public Member getLocalMember(boolean incAlive);
+
+    /**
+     * Returns the member from the membership service with complete and
+     * recent data. Some implementations might serialize and send
+     * membership information along with a message, and instead of sending
+     * complete membership details, only send the primary identifier for the member
+     * but not the payload or other information. When such message is received
+     * the application can retrieve the cached member through this call.<br>
+     * In most cases, this is not necessary.
+     * @param mbr Member
+     * @return Member
+     */
+    public Member getMember(Member mbr);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelException.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelException.java
new file mode 100644
index 0000000..dc41756
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelException.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.util.ArrayList;
+
+/**
+ * Channel Exception<br>
+ * A channel exception is thrown when an internal error happens
+ * somewhere in the channel. <br>
+ * When a global error happens, the cause can be retrieved using <code>getCause()</code><br><br>
+ * If an application is sending a message and some of the recipients fail to receive it,
+ * the application can retrieve what recipients failed by using the <code>getFaultyMembers()</code>
+ * method. This way, an application will always know if a message was delivered successfully or not.
+ * @author Filip Hanik
+ * @version $Id: ChannelException.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class ChannelException extends Exception {
+    private static final long serialVersionUID = 1L;
+    /**
+     * Empty list to avoid reinstatiating lists
+     */
+    protected static final FaultyMember[] EMPTY_LIST = new FaultyMember[0];
+    /*
+     * Holds a list of faulty members
+     */
+    private ArrayList<FaultyMember> faultyMembers=null;
+    
+    /**
+     * Constructor, creates a ChannelException
+     * @see java.lang.Exception#Exception()
+     */
+    public ChannelException() {
+        super();
+    }
+
+    /**
+     * Constructor, creates a ChannelException with an error message
+     * @see java.lang.Exception#Exception(String)
+     */
+    public ChannelException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor, creates a ChannelException with an error message and a cause
+     * @param message String
+     * @param cause Throwable
+     * @see java.lang.Exception#Exception(String,Throwable)
+     */
+    public ChannelException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructor, creates a ChannelException with a cause
+     * @param cause Throwable
+     * @see java.lang.Exception#Exception(Throwable)
+     */
+    public ChannelException(Throwable cause) {
+        super(cause);
+    }
+    
+    /**
+     * Returns the message for this exception
+     * @return String
+     * @see java.lang.Exception#getMessage()
+     */
+    @Override
+    public String getMessage() {
+        StringBuilder buf = new StringBuilder(super.getMessage());
+        if (faultyMembers==null || faultyMembers.size() == 0 ) {
+            buf.append("; No faulty members identified.");
+        } else {
+            buf.append("; Faulty members:");
+            for ( int i=0; i<faultyMembers.size(); i++ ) {
+                FaultyMember mbr = faultyMembers.get(i);
+                buf.append(mbr.getMember().getName());
+                buf.append("; ");
+            }
+        }
+        return buf.toString();
+    }
+    
+    /**
+     * Adds a faulty member, and the reason the member failed.
+     * @param mbr Member
+     * @param x Exception
+     */
+    public boolean addFaultyMember(Member mbr, Exception x ) {
+        return addFaultyMember(new FaultyMember(mbr,x));
+    }
+    
+    /**
+     * Adds a list of faulty members
+     * @param mbrs FaultyMember[]
+     */
+    public int addFaultyMember(FaultyMember[] mbrs) {
+        int result = 0;
+        for (int i=0; mbrs!=null && i<mbrs.length; i++ ) {
+            if ( addFaultyMember(mbrs[i]) ) result++;
+        }
+        return result;
+    }
+
+    /**
+     * Adds a faulty member
+     * @param mbr FaultyMember
+     */
+    public boolean addFaultyMember(FaultyMember mbr) {
+        if ( this.faultyMembers==null ) this.faultyMembers = new ArrayList<FaultyMember>();
+        if ( !faultyMembers.contains(mbr) ) return faultyMembers.add(mbr);
+        else return false;
+    }
+    
+    /**
+     * Returns an array of members that failed and the reason they failed.
+     * @return FaultyMember[]
+     */
+    public FaultyMember[] getFaultyMembers() {
+        if ( this.faultyMembers==null ) return EMPTY_LIST;
+        return faultyMembers.toArray(new FaultyMember[faultyMembers.size()]);
+    }
+    
+    /**
+     * 
+     * <p>Title: FaultyMember class</p> 
+     * 
+     * <p>Description: Represent a failure to a specific member when a message was sent
+     * to more than one member</p> 
+     * 
+     * @author Filip Hanik
+     * @version 1.0
+     */
+    public static class FaultyMember {
+        protected Exception cause;
+        protected Member member;
+        public FaultyMember(Member mbr, Exception x) { 
+            this.member = mbr;
+            this.cause = x;
+        }
+        
+        public Member getMember() {
+            return member;
+        }
+        
+        public Exception getCause() {
+            return cause;
+        }
+        
+        @Override
+        public String toString() {
+            return "FaultyMember:"+member.toString();
+        }
+        
+        @Override
+        public int hashCode() {
+            return (member!=null)?member.hashCode():0;
+        }
+        
+        @Override
+        public boolean equals(Object o) {
+            if (member==null || (!(o instanceof FaultyMember)) || (((FaultyMember)o).member==null)) return false;
+            return member.equals(((FaultyMember)o).member);
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelInterceptor.java
new file mode 100644
index 0000000..909c2a5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelInterceptor.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import org.apache.catalina.tribes.group.InterceptorPayload;
+
+/**
+ * A ChannelInterceptor is an interceptor that intercepts 
+ * messages and membership messages in the channel stack.
+ * This allows interceptors to modify the message or perform
+ * other actions when a message is sent or received.<br>
+ * Interceptors are tied together in a linked list.
+ * @see org.apache.catalina.tribes.group.ChannelInterceptorBase
+ * @author Filip Hanik
+ * @version $Id: ChannelInterceptor.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */   
+
+public interface ChannelInterceptor extends MembershipListener, Heartbeat {
+
+    /**
+     * An interceptor can react to a message based on a set bit on the 
+     * message options. <br>
+     * When a message is sent, the options can be retrieved from ChannelMessage.getOptions()
+     * and if the bit is set, this interceptor will react to it.<br>
+     * A simple evaluation if an interceptor should react to the message would be:<br>
+     * <code>boolean react = (getOptionFlag() == (getOptionFlag() & ChannelMessage.getOptions()));</code><br>
+     * The default option is 0, meaning there is no way for the application to trigger the
+     * interceptor. The interceptor itself will decide.<br>
+     * @return int
+     * @see ChannelMessage#getOptions()
+     */
+    public int getOptionFlag();
+    
+    /**
+     * Sets the option flag
+     * @param flag int
+     * @see #getOptionFlag()
+     */
+    public void setOptionFlag(int flag);
+
+    /**
+     * Set the next interceptor in the list of interceptors
+     * @param next ChannelInterceptor
+     */
+    public void setNext(ChannelInterceptor next) ;
+
+    /**
+     * Retrieve the next interceptor in the list
+     * @return ChannelInterceptor - returns the next interceptor in the list or null if no more interceptors exist
+     */
+    public ChannelInterceptor getNext();
+
+    /**
+     * Set the previous interceptor in the list
+     * @param previous ChannelInterceptor
+     */
+    public void setPrevious(ChannelInterceptor previous);
+
+    /**
+     * Retrieve the previous interceptor in the list
+     * @return ChannelInterceptor - returns the previous interceptor in the list or null if no more interceptors exist
+     */
+    public ChannelInterceptor getPrevious();
+
+    /**
+     * The <code>sendMessage</code> method is called when a message is being sent to one more destinations.
+     * The interceptor can modify any of the parameters and then pass on the message down the stack by
+     * invoking <code>getNext().sendMessage(destination,msg,payload)</code><br>
+     * Alternatively the interceptor can stop the message from being sent by not invoking 
+     * <code>getNext().sendMessage(destination,msg,payload)</code><br>
+     * If the message is to be sent asynchronous the application can be notified of completion and 
+     * errors by passing in an error handler attached to a payload object.<br>
+     * The ChannelMessage.getAddress contains Channel.getLocalMember, and can be overwritten 
+     * to simulate a message sent from another node.<br>
+     * @param destination Member[] - the destination for this message
+     * @param msg ChannelMessage - the message to be sent
+     * @param payload InterceptorPayload - the payload, carrying an error handler and future useful data, can be null
+     * @throws ChannelException
+     * @see ErrorHandler
+     * @see InterceptorPayload
+     */
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException;
+    
+    /**
+     * the <code>messageReceived</code> is invoked when a message is received.
+     * <code>ChannelMessage.getAddress()</code> is the sender, or the reply-to address
+     * if it has been overwritten.
+     * @param data ChannelMessage
+     */
+    public void messageReceived(ChannelMessage data);
+    
+    /**
+     * The <code>heartbeat()</code> method gets invoked periodically
+     * to allow interceptors to clean up resources, time out object and 
+     * perform actions that are unrelated to sending/receiving data.
+     */
+    public void heartbeat();
+    
+    /**
+     * Intercepts the <code>Channel.hasMembers()</code> method
+     * @return boolean - if the channel has members in its membership group
+     * @see Channel#hasMembers()
+     */
+    public boolean hasMembers() ;
+
+    /**
+     * Intercepts the code>Channel.getMembers()</code> method
+     * @return Member[]
+     * @see Channel#getMembers()
+     */
+    public Member[] getMembers() ;
+
+    /**
+     * Intercepts the code>Channel.getLocalMember(boolean)</code> method
+     * @param incAliveTime boolean
+     * @return Member
+     * @see Channel#getLocalMember(boolean)
+     */
+    public Member getLocalMember(boolean incAliveTime) ;
+
+    /**
+     * Intercepts the code>Channel.getMember(Member)</code> method
+     * @param mbr Member
+     * @return Member - the actual member information, including stay alive
+     * @see Channel#getMember(Member)
+     */
+    public Member getMember(Member mbr);
+    
+    /**
+     * Starts up the channel. This can be called multiple times for individual services to start
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * Channel.DEFAULT - will start all services <BR>
+     * Channel.MBR_RX_SEQ - starts the membership receiver <BR>
+     * Channel.MBR_TX_SEQ - starts the membership broadcaster <BR>
+     * Channel.SND_TX_SEQ - starts the replication transmitter<BR>
+     * Channel.SND_RX_SEQ - starts the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     * @see Channel
+     */
+    public void start(int svc) throws ChannelException;
+
+    /**
+     * Shuts down the channel. This can be called multiple times for individual services to shutdown
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * Channel.DEFAULT - will shutdown all services <BR>
+     * Channel.MBR_RX_SEQ - stops the membership receiver <BR>
+     * Channel.MBR_TX_SEQ - stops the membership broadcaster <BR>
+     * Channel.SND_TX_SEQ - stops the replication transmitter<BR>
+     * Channel.SND_RX_SEQ - stops the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     * @see Channel
+     */
+    public void stop(int svc) throws ChannelException;
+    
+    public void fireInterceptorEvent(InterceptorEvent event);
+
+    interface InterceptorEvent {
+        int getEventType();
+        String getEventTypeDesc();
+        ChannelInterceptor getInterceptor();
+    }
+    
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelListener.java
new file mode 100644
index 0000000..ea81f2e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelListener.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+/**
+ * 
+ * <p>Title: ChannelListener</p> 
+ * 
+ * <p>Description: An interface to listens to incoming messages from a channel </p> 
+ * When a message is received, the Channel will invoke the channel listener in a conditional sequence.
+ * <code>if ( listener.accept(msg,sender) ) listener.messageReceived(msg,sender);</code><br>
+ * A ChannelListener implementation MUST NOT return true on <code>accept(Serializable, Member)</code>
+ * if it doesn't intend to process the message. The channel can this way track whether a message
+ * was processed by an above application or if it was just received and forgot about, a feature required
+ * to support message-response(RPC) calls<br>
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public interface ChannelListener {
+
+    /**
+     * Receive a message from the channel
+     * @param msg Serializable
+     * @param sender - the source of the message
+     */
+    public void messageReceived(Serializable msg, Member sender);
+
+    /**
+     * Invoked by the channel to determine if the listener will process this message or not.
+     * @param msg Serializable
+     * @param sender Member
+     * @return boolean
+     */
+    public boolean accept(Serializable msg, Member sender);
+
+    /**
+     * 
+     * @param listener Object
+     * @return boolean
+     * @see Object#equals(Object)
+     */
+    public boolean equals(Object listener);
+
+    /**
+     * 
+     * @return int
+     * @see Object#hashCode()
+     */
+    public int hashCode();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelMessage.java
new file mode 100644
index 0000000..4596b81
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelMessage.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+/**
+ * Message that is passed through the interceptor stack after the 
+ * data serialized in the Channel object and then passed down to the 
+ * interceptor and eventually down to the ChannelSender component
+ * @author Filip Hanik
+ * 
+ */
+public interface ChannelMessage extends Serializable {
+
+
+    /**
+     * Get the address that this message originated from.  
+     * Almost always <code>Channel.getLocalMember(boolean)</code><br>
+     * This would be set to a different address 
+     * if the message was being relayed from a host other than the one
+     * that originally sent it.
+     * @return the source or reply-to address of this message
+     */
+    public Member getAddress();
+
+    /**
+     * Sets the source or reply-to address of this message
+     * @param member Member
+     */
+    public void setAddress(Member member);
+
+    /**
+     * Timestamp of when the message was created.
+     * @return long timestamp in milliseconds
+     */
+    public long getTimestamp();
+
+    /**
+     *
+     * Sets the timestamp of this message
+     * @param timestamp The timestamp
+     */
+    public void setTimestamp(long timestamp);
+
+    /**
+     * Each message must have a globally unique Id.
+     * interceptors heavily depend on this id for message processing
+     * @return byte
+     */
+    public byte[] getUniqueId();
+    
+    /**
+     * The byte buffer that contains the actual message payload
+     * @param buf XByteBuffer
+     */
+    public void setMessage(XByteBuffer buf);
+    
+    /**
+     * returns the byte buffer that contains the actual message payload
+     * @return XByteBuffer
+     */
+    public XByteBuffer getMessage();
+    
+    /**
+     * The message options is a 32 bit flag set
+     * that triggers interceptors and message behavior.
+     * @see Channel#send(Member[], Serializable, int) 
+     * @see ChannelInterceptor#getOptionFlag
+     * @return int - the option bits set for this message
+     */
+    public int getOptions();
+    
+    /**
+     * sets the option bits for this message
+     * @param options int
+     * @see #getOptions()
+     */
+    public void setOptions(int options);
+    
+    /**
+     * Shallow clone, what gets cloned depends on the implementation
+     * @return ChannelMessage
+     */
+    public Object clone();
+
+    /**
+     * Deep clone, all fields MUST get cloned
+     * @return ChannelMessage
+     */
+    public Object deepclone();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelReceiver.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelReceiver.java
new file mode 100644
index 0000000..a9d11d6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelReceiver.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+
+/**
+ * ChannelReceiver Interface<br>
+ * The <code>ChannelReceiver</code> interface is the data receiver component 
+ * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).
+ * This class may optionally implement a thread pool for parallel processing of incoming messages.
+ * @author Filip Hanik
+ * @version $Id: ChannelReceiver.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+public interface ChannelReceiver extends Heartbeat {
+    public static final int MAX_UDP_SIZE = 65535;
+    
+    /**
+     * Start listening for incoming messages on the host/port
+     * @throws java.io.IOException
+     */
+    public void start() throws java.io.IOException;
+
+    /**
+     * Stop listening for messages
+     */
+    public void stop();
+
+    /**
+     * String representation of the IPv4 or IPv6 address that this host is listening
+     * to.
+     * @return the host that this receiver is listening to
+     */
+    public String getHost();
+    
+    
+    /**
+     * Returns the listening port
+     * @return port
+     */
+    public int getPort();
+    
+    /**
+     * Returns the secure listening port
+     * @return port, -1 if a secure port is not activated
+     */
+    public int getSecurePort();
+    
+    /**
+     * Returns the UDP port
+     * @return port, -1 if the UDP port is not activated.
+     */
+    public int getUdpPort();
+    
+    /**
+     * Sets the message listener to receive notification of incoming
+     * @param listener MessageListener
+     * @see MessageListener
+     */
+    public void setMessageListener(MessageListener listener);
+    
+    /**
+     * Returns the message listener that is associated with this receiver
+     * @return MessageListener
+     * @see MessageListener
+     */
+    public MessageListener getMessageListener();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelSender.java
new file mode 100644
index 0000000..1683971
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ChannelSender.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+import java.io.IOException;
+
+
+/**
+ * ChannelReceiver Interface<br>
+ * The <code>ChannelSender</code> interface is the data sender component 
+ * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).<br>
+ * The channel sender must support "silent" members, ie, be able to send a message to a member
+ * that is not in the membership, but is part of the destination parameter
+ * @author Filip Hanik
+ * @version $Id: ChannelSender.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+public interface ChannelSender extends Heartbeat
+{
+    /**
+     * Notify the sender of a member being added to the group.<br>
+     * Optional. This can be an empty implementation, that does nothing
+     * @param member Member
+     */
+    public void add(Member member);
+    /**
+     * Notification that a member has been removed or crashed.
+     * Can be used to clean up open connections etc
+     * @param member Member
+     */
+    public void remove(Member member);
+    
+    /**
+     * Start the channel sender
+     * @throws IOException if preprocessing takes place and an error happens
+     */
+    public void start() throws IOException;
+
+    /**
+     * Stop the channel sender
+     */
+    public void stop();
+    
+    /**
+     * A channel heartbeat, use this method to clean up resources
+     */
+    @Override
+    public void heartbeat() ;
+    
+    /**
+     * Send a message to one or more recipients.
+     * @param message ChannelMessage - the message to be sent
+     * @param destination Member[] - the destinations
+     * @throws ChannelException - if an error happens, the ChannelSender MUST report
+     * individual send failures on a per member basis, using ChannelException.addFaultyMember
+     * @see ChannelException#addFaultyMember(Member,java.lang.Exception)
+     */
+    public void sendMessage(ChannelMessage message, Member[] destination) throws ChannelException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Constants.java
new file mode 100644
index 0000000..830e2ab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Constants.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.tribes;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.tribes</code>
+ * package.
+ *
+ * @author Bip Thelin
+ * @author Filip Hanik
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public final class Constants {
+    public static final String Package = "org.apache.catalina.tribes";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ErrorHandler.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ErrorHandler.java
new file mode 100644
index 0000000..db5de4f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ErrorHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+
+
+/**
+ * The <code>ErrorHandler</code> class is used when sending messages
+ * that are sent asynchronously and the application still needs to get 
+ * confirmation when the message was sent successfully or when a message errored out.
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public interface ErrorHandler {
+    
+    /**
+     * Invoked if the message is dispatched asynch, and an error occurs
+     * @param x ChannelException - the error that happened
+     * @param id - the unique id for the message
+     * @see Channel#send(Member[], Serializable, int, ErrorHandler)
+     */
+    public void handleError(ChannelException x, UniqueId id);
+    
+    /**
+     * Invoked when the message has been sent successfully.
+     * @param id - the unique id for the message
+     * @see Channel#send(Member[], Serializable, int, ErrorHandler)
+     */
+    public void handleCompletion(UniqueId id);
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Heartbeat.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Heartbeat.java
new file mode 100644
index 0000000..95065e5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Heartbeat.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+/**
+ * Can be implemented by the ChannelListener and Membership listeners to receive heartbeat
+ * notifications from the Channel
+ * @author Filip Hanik
+ * @version 1.0
+ * @see Channel
+ * @see Channel#heartbeat()
+ */
+public interface Heartbeat {
+    
+    /**
+     * Heartbeat invocation for resources cleanup etc
+     */
+    public void heartbeat();
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ManagedChannel.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ManagedChannel.java
new file mode 100644
index 0000000..871b15b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/ManagedChannel.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.util.Iterator;
+
+/**
+ * Channel interface
+ * A managed channel interface gives you access to the components of the channels
+ * such as senders, receivers, interceptors etc for configurations purposes
+ * @author Filip Hanik
+ * @version $Id: ManagedChannel.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+public interface ManagedChannel extends Channel {
+
+    /**
+     * Sets the channel sender
+     * @param sender ChannelSender
+     * @see ChannelSender
+     */
+    public void setChannelSender(ChannelSender sender);
+
+    /**
+     * Sets the channel receiver
+     * @param receiver ChannelReceiver
+     * @see ChannelReceiver
+     */
+    public void setChannelReceiver(ChannelReceiver receiver);
+    
+    /**
+     * Sets the membership service
+     * @param service MembershipService
+     * @see MembershipService
+     */
+    public void setMembershipService(MembershipService service);
+
+    /**
+     * returns the channel sender
+     * @return ChannelSender
+     * @see ChannelSender
+     */
+    public ChannelSender getChannelSender();
+    
+    /**
+     * returns the channel receiver
+     * @return ChannelReceiver
+     * @see ChannelReceiver
+     */
+    public ChannelReceiver getChannelReceiver();
+    
+    /**
+     * Returns the membership service
+     * @return MembershipService
+     * @see MembershipService
+     */
+    public MembershipService getMembershipService();
+
+    /**
+     * Returns the interceptor stack
+     * @return Iterator
+     * @see Channel#addInterceptor(ChannelInterceptor)
+     */
+    public Iterator<ChannelInterceptor> getInterceptors();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Member.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Member.java
new file mode 100644
index 0000000..3fb7561
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/Member.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+/**
+ * The Member interface, defines a member in the group.
+ * Each member can carry a set of properties, defined by the actual implementation.<BR>
+ * A member is identified by the host/ip/uniqueId<br>
+ * The host is what interface the member is listening to, to receive data<br>
+ * The port is what port the member is listening to, to receive data<br>
+ * The uniqueId defines the session id for the member. This is an important feature
+ * since a member that has crashed and the starts up again on the same port/host is
+ * not guaranteed to be the same member, so no state transfers will ever be confused
+ * @author Filip Hanik
+ * @version $Id: Member.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+
+public interface Member {
+
+    /**
+     * When a member leaves the cluster, the payload of the memberDisappeared member
+     * will be the following bytes. This indicates a soft shutdown, and not a crash
+     */
+    public static final byte[] SHUTDOWN_PAYLOAD = new byte[] {66, 65, 66, 89, 45, 65, 76, 69, 88};
+
+    /**
+     * Returns the name of this node, should be unique within the group.
+     */
+    public String getName();
+
+    /**
+     * Returns the listen host for the ChannelReceiver implementation
+     * @return IPv4 or IPv6 representation of the host address this member listens to incoming data
+     * @see ChannelReceiver
+     */
+    public byte[] getHost();
+
+    /**
+     * Returns the listen port for the ChannelReceiver implementation
+     * @return the listen port for this member, -1 if its not listening on an insecure port
+     * @see ChannelReceiver
+     */
+    public int getPort();
+
+    /**
+     * Returns the secure listen port for the ChannelReceiver implementation.
+     * Returns -1 if its not listening to a secure port.
+     * @return the listen port for this member, -1 if its not listening on a secure port
+     * @see ChannelReceiver
+     */
+    public int getSecurePort();
+
+    /**
+     * Returns the UDP port that this member is listening to for UDP messages.
+     * @return the listen UDP port for this member, -1 if its not listening on a UDP port
+     */
+    public int getUdpPort();
+
+
+    /**
+     * Contains information on how long this member has been online.
+     * The result is the number of milli seconds this member has been
+     * broadcasting its membership to the group.
+     * @return nr of milliseconds since this member started.
+     */
+    public long getMemberAliveTime();
+
+    /**
+     * The current state of the member
+     * @return boolean - true if the member is functioning correctly
+     */
+    public boolean isReady();
+    /**
+     * The current state of the member
+     * @return boolean - true if the member is suspect, but the crash has not been confirmed
+     */
+    public boolean isSuspect();
+
+    /**
+     *
+     * @return boolean - true if the member has been confirmed to malfunction
+     */
+    public boolean isFailing();
+
+    /**
+     * returns a UUID unique for this member over all sessions.
+     * If the member crashes and restarts, the uniqueId will be different.
+     * @return byte[]
+     */
+    public byte[] getUniqueId();
+
+    /**
+     * returns the payload associated with this member
+     * @return byte[]
+     */
+    public byte[] getPayload();
+
+    /**
+     * returns the command associated with this member
+     * @return byte[]
+     */
+    public byte[] getCommand();
+
+    /**
+     * Domain for this cluster
+     * @return byte[]
+     */
+    public byte[] getDomain();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MembershipListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MembershipListener.java
new file mode 100644
index 0000000..6f146ff
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MembershipListener.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+/**
+ * The MembershipListener interface is used as a callback to the
+ * membership service. It has two methods that will notify the listener
+ * when a member has joined the group and when a member has disappeared (crashed)
+ *
+ * @author Filip Hanik
+ * @version $Id: MembershipListener.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+
+public interface MembershipListener {
+    /**
+     * A member was added to the group
+     * @param member Member - the member that was added
+     */
+    public void memberAdded(Member member);
+    
+    /**
+     * A member was removed from the group<br>
+     * If the member left voluntarily, the Member.getCommand will contain the Member.SHUTDOWN_PAYLOAD data
+     * @param member Member
+     * @see Member#SHUTDOWN_PAYLOAD
+     */
+    public void memberDisappeared(Member member);
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MembershipService.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MembershipService.java
new file mode 100644
index 0000000..fc60309
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MembershipService.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes;
+
+
+/**
+ * MembershipService Interface<br>
+ * The <code>MembershipService</code> interface is the membership component 
+ * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).<br>
+ * @author Filip Hanik
+ * @version $Id: MembershipService.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+
+public interface MembershipService {
+    
+    public static final int MBR_RX = Channel.MBR_RX_SEQ;
+    public static final int MBR_TX = Channel.MBR_TX_SEQ;
+    
+    /**
+     * Sets the properties for the membership service. This must be called before
+     * the <code>start()</code> method is called.
+     * The properties are implementation specific.
+     * @param properties - to be used to configure the membership service.
+     */
+    public void setProperties(java.util.Properties properties);
+    /**
+     * Returns the properties for the configuration used.
+     */
+    public java.util.Properties getProperties();
+    /**
+     * Starts the membership service. If a membership listeners is added
+     * the listener will start to receive membership events.
+     * Performs a start level 1 and 2
+     * @throws java.lang.Exception if the service fails to start.
+     */
+    public void start() throws java.lang.Exception;
+
+    /**
+     * Starts the membership service. If a membership listeners is added
+     * the listener will start to receive membership events.
+     * @param level - level MBR_RX starts listening for members, level MBR_TX 
+     * starts broad casting the server
+     * @throws java.lang.Exception if the service fails to start.
+     * @throws java.lang.IllegalArgumentException if the level is incorrect.
+     */
+    public void start(int level) throws java.lang.Exception;
+
+
+    /**
+     * Starts the membership service. If a membership listeners is added
+     * the listener will start to receive membership events.
+     * @param level - level MBR_RX stops listening for members, level MBR_TX 
+     * stops broad casting the server
+     * @throws java.lang.Exception if the service fails to stop
+     * @throws java.lang.IllegalArgumentException if the level is incorrect.
+     */
+
+    public void stop(int level);
+    
+    /**
+     * @return true if the the group contains members
+     */
+    public boolean hasMembers();
+    
+    
+    /**
+     * 
+     * @param mbr Member
+     * @return Member
+     */
+    public Member getMember(Member mbr);
+    /**
+     * Returns a list of all the members in the cluster.
+     */
+    
+    public Member[] getMembers();
+    
+    /**
+     * Returns the member object that defines this member
+     */
+    public Member getLocalMember(boolean incAliveTime);
+
+    /**
+     * Return all members by name
+     */
+    public String[] getMembersByName() ; 
+    
+    /**
+     * Return the member by name
+     */
+    public Member findMemberByName(String name) ;
+
+    /**
+     * Sets the local member properties for broadcasting
+     */
+    public void setLocalMemberProperties(String listenHost, int listenPort, int securePort, int udpPort);
+    
+    /**
+     * Sets the membership listener, only one listener can be added.
+     * If you call this method twice, the last listener will be used.
+     * @param listener The listener
+     */
+    public void setMembershipListener(MembershipListener listener);
+    
+    /**
+     * removes the membership listener.
+     */
+    public void removeMembershipListener();
+    
+    /**
+     * Set a payload to be broadcasted with each membership 
+     * broadcast.
+     * @param payload byte[]
+     */
+    public void setPayload(byte[] payload);
+    
+    public void setDomain(byte[] domain);
+    
+    /**
+     * Broadcasts a message to all members
+     * @param message
+     * @throws ChannelException
+     */
+    public void broadcast(ChannelMessage message) throws ChannelException;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MessageListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MessageListener.java
new file mode 100644
index 0000000..02adf5a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/MessageListener.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+/**
+ * 
+ * <p>Title: MessageListener</p> 
+ * 
+ * <p>Description: The listener to be registered with the ChannelReceiver, internal Tribes component</p> 
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public interface MessageListener {
+    
+    /**
+     * Receive a message from the IO components in the Channel stack
+     * @param msg ChannelMessage
+     */
+    public void messageReceived(ChannelMessage msg);
+    
+    public boolean accept(ChannelMessage msg);
+    
+    public boolean equals(Object listener);
+    
+    public int hashCode();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/RemoteProcessException.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/RemoteProcessException.java
new file mode 100644
index 0000000..51885a1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/RemoteProcessException.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+/**
+ * <p>Title: RemoteProcessException</p>
+ *
+ * <p>Description: Message thrown by a sender when USE_SYNC_ACK receives a FAIL_ACK_COMMAND.<br>
+ * This means that the message was received on the remote node but the processing of the message failed.
+ * This message will be embedded in a ChannelException.FaultyMember
+ * </p>
+ * @see ChannelException
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class RemoteProcessException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    public RemoteProcessException() {
+        super();
+    }
+
+    public RemoteProcessException(String message) {
+        super(message);
+    }
+
+    public RemoteProcessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public RemoteProcessException(Throwable cause) {
+        super(cause);
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/UniqueId.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/UniqueId.java
new file mode 100644
index 0000000..2bec537
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/UniqueId.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.util.Arrays;
+
+/**
+ * <p>Title: Represents a globally unique Id</p>
+ *
+ * <p>Company: </p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public final class UniqueId implements Serializable{
+    private static final long serialVersionUID = 1L;
+
+    protected byte[] id;
+    
+    public UniqueId() {
+    }
+
+    public UniqueId(byte[] id) {
+        this.id = id;
+    }
+    
+    public UniqueId(byte[] id, int offset, int length) {
+        this.id = new byte[length];
+        System.arraycopy(id,offset,this.id,0,length);
+    }
+    
+    @Override
+    public int hashCode() {
+        if ( id == null ) return 0;
+        return Arrays.hashCode(id);
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        boolean result = (other instanceof UniqueId);
+        if ( result ) {
+            UniqueId uid = (UniqueId)other;
+            if ( this.id == null && uid.id == null ) result = true;
+            else if ( this.id == null && uid.id != null ) result = false;
+            else if ( this.id != null && uid.id == null ) result = false;
+            else result = Arrays.equals(this.id,uid.id);
+        }//end if
+        return result;
+    }
+    
+    public byte[] getBytes() {
+        return id;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder("UniqueId");
+        buf.append(org.apache.catalina.tribes.util.Arrays.toString(id));
+        return buf.toString();
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/AbsoluteOrder.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/AbsoluteOrder.java
new file mode 100644
index 0000000..e806a33
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/AbsoluteOrder.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * <p>Title: Membership - Absolute Order</p>
+ *
+ * <p>Description: A simple, yet agreeable and efficient way of ordering members</p>
+ * <p>
+ *    Ordering members can serve as a basis for electing a leader or coordinating efforts.<br>
+ *    This is stinky simple, it works on the basis of the <code>Member</code> interface
+ *    and orders members in the following format:
+ * 
+ *  <ol>
+ *     <li>IP comparison - byte by byte, lower byte higher rank</li>
+ *     <li>IPv4 addresses rank higher than IPv6, ie the lesser number of bytes, the higher rank</li>
+ *     <li>Port comparison - lower port, higher rank</li>
+ *     <li>UniqueId comparison- byte by byte, lower byte higher rank</li>
+ *  </ol>
+ *     
+ * </p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ * @see org.apache.catalina.tribes.Member
+ */
+public class AbsoluteOrder {
+    public static final AbsoluteComparator comp = new AbsoluteComparator();
+    
+    protected AbsoluteOrder() {
+        super();
+    }
+
+
+    public static void absoluteOrder(Member[] members) {
+        if ( members == null || members.length <= 1 ) return;
+        Arrays.sort(members,comp);
+    }
+    
+    public static void absoluteOrder(List<Member> members) {
+        if ( members == null || members.size() <= 1 ) return;
+        java.util.Collections.sort(members, comp);
+    }
+    
+    public static class AbsoluteComparator implements Comparator<Member>,
+            Serializable {
+        
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public int compare(Member m1, Member m2) {
+            int result = compareIps(m1,m2);
+            if ( result == 0 ) result = comparePorts(m1,m2);
+            if ( result == 0 ) result = compareIds(m1,m2);
+            return result;
+        }
+        
+        public int compareIps(Member m1, Member m2) {
+            return compareBytes(m1.getHost(),m2.getHost());
+        }
+        
+        public int comparePorts(Member m1, Member m2) {
+            return compareInts(m1.getPort(),m2.getPort());
+        }
+        
+        public int compareIds(Member m1, Member m2) {
+            return compareBytes(m1.getUniqueId(),m2.getUniqueId());
+        }
+        
+        protected int compareBytes(byte[] d1, byte[] d2) {
+            int result = 0;
+            if ( d1.length == d2.length ) {
+                for (int i=0; (result==0) && (i<d1.length); i++) {
+                    result = compareBytes(d1[i],d2[i]);
+                }
+            } else if ( d1.length < d2.length) {
+                result = -1;
+            } else {
+                result = 1;
+            }
+            return result;
+        }
+        
+        protected int compareBytes(byte b1, byte b2) {
+            return compareInts(b1,b2);
+        }
+        
+        protected int compareInts(int b1, int b2) {
+            int result = 0;
+            if ( b1 == b2 ) {
+
+            } else if ( b1 < b2) {
+                result = -1;
+            } else {
+                result = 1;
+            }
+            return result;
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ChannelCoordinator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ChannelCoordinator.java
new file mode 100644
index 0000000..fa2e2e9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ChannelCoordinator.java
@@ -0,0 +1,332 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.ChannelSender;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.MessageListener;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.membership.McastService;
+import org.apache.catalina.tribes.transport.ReplicationTransmitter;
+import org.apache.catalina.tribes.transport.SenderState;
+import org.apache.catalina.tribes.transport.nio.NioReceiver;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.util.Logs;
+
+
+/**
+ * The channel coordinator object coordinates the membership service,
+ * the sender and the receiver.
+ * This is the last interceptor in the chain.
+ * @author Filip Hanik
+ * @version $Id: ChannelCoordinator.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public class ChannelCoordinator extends ChannelInterceptorBase implements MessageListener {
+    private ChannelReceiver clusterReceiver = new NioReceiver();
+    private ChannelSender clusterSender = new ReplicationTransmitter();
+    private MembershipService membershipService = new McastService();
+    
+    private int startLevel = 0;
+
+    public ChannelCoordinator() {
+        // Override default
+        this.optionFlag = Channel.SEND_OPTIONS_BYTE_MESSAGE |
+                Channel.SEND_OPTIONS_USE_ACK |
+                Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
+    }
+    
+    public ChannelCoordinator(ChannelReceiver receiver,
+                              ChannelSender sender,
+                              MembershipService service) {
+        this();
+        this.setClusterReceiver(receiver);
+        this.setClusterSender(sender);
+        this.setMembershipService(service);
+    }
+    
+    /**
+     * Send a message to one or more members in the cluster
+     * @param destination Member[] - the destinations, null or zero length means all
+     * @param msg ClusterMessage - the message to send
+     * @param payload TBA
+     */
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        if ( destination == null ) destination = membershipService.getMembers();
+        if ((msg.getOptions()&Channel.SEND_OPTIONS_MULTICAST) == Channel.SEND_OPTIONS_MULTICAST) {
+            membershipService.broadcast(msg);
+        } else {
+            clusterSender.sendMessage(msg,destination);
+        }
+        if ( Logs.MESSAGES.isTraceEnabled() ) {
+            Logs.MESSAGES.trace("ChannelCoordinator - Sent msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+Arrays.toNameString(destination));
+        }
+    }
+    
+
+    /**
+     * Starts up the channel. This can be called multiple times for individual services to start
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will start all services <BR>
+     * MBR_RX_SEQ - starts the membership receiver <BR>
+     * MBR_TX_SEQ - starts the membership broadcaster <BR>
+     * SND_TX_SEQ - starts the replication transmitter<BR>
+     * SND_RX_SEQ - starts the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     */
+    @Override
+    public void start(int svc) throws ChannelException {
+        this.internalStart(svc);
+    }
+
+    /**
+     * Shuts down the channel. This can be called multiple times for individual services to shutdown
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will shutdown all services <BR>
+     * MBR_RX_SEQ - stops the membership receiver <BR>
+     * MBR_TX_SEQ - stops the membership broadcaster <BR>
+     * SND_TX_SEQ - stops the replication transmitter<BR>
+     * SND_RX_SEQ - stops the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     */
+    @Override
+    public void stop(int svc) throws ChannelException {
+        this.internalStop(svc);
+    }    
+
+
+    /**
+     * Starts up the channel. This can be called multiple times for individual services to start
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will start all services <BR>
+     * MBR_RX_SEQ - starts the membership receiver <BR>
+     * MBR_TX_SEQ - starts the membership broadcaster <BR>
+     * SND_TX_SEQ - starts the replication transmitter<BR>
+     * SND_RX_SEQ - starts the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     */
+    protected synchronized void internalStart(int svc) throws ChannelException {
+        try {
+            boolean valid = false;
+            //make sure we don't pass down any flags that are unrelated to the bottom layer
+            svc = svc & Channel.DEFAULT;
+
+            if (startLevel == Channel.DEFAULT) return; //we have already started up all components
+            if (svc == 0 ) return;//nothing to start
+            
+            if (svc == (svc & startLevel)) throw new ChannelException("Channel already started for level:"+svc);
+
+            //must start the receiver first so that we can coordinate the port it
+            //listens to with the local membership settings
+            if ( Channel.SND_RX_SEQ==(svc & Channel.SND_RX_SEQ) ) {
+                clusterReceiver.setMessageListener(this);
+                clusterReceiver.start();
+                //synchronize, big time FIXME
+                membershipService.setLocalMemberProperties(getClusterReceiver().getHost(), 
+                                                           getClusterReceiver().getPort(),
+                                                           getClusterReceiver().getSecurePort(),
+                                                           getClusterReceiver().getUdpPort());
+                valid = true;
+            }
+            if ( Channel.SND_TX_SEQ==(svc & Channel.SND_TX_SEQ) ) {
+                clusterSender.start();
+                valid = true;
+            }
+            
+            if ( Channel.MBR_RX_SEQ==(svc & Channel.MBR_RX_SEQ) ) {
+                membershipService.setMembershipListener(this);
+                if (membershipService instanceof McastService) {
+                    ((McastService)membershipService).setMessageListener(this);
+                }
+                membershipService.start(MembershipService.MBR_RX);
+                valid = true;
+            }
+            if ( Channel.MBR_TX_SEQ==(svc & Channel.MBR_TX_SEQ) ) {
+                membershipService.start(MembershipService.MBR_TX);
+                valid = true;
+            }
+            
+            if ( !valid) {
+                throw new IllegalArgumentException("Invalid start level, valid levels are:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ");
+            }
+            startLevel = (startLevel | svc);
+        }catch ( ChannelException cx ) {
+            throw cx;
+        }catch ( Exception x ) {
+            throw new ChannelException(x);
+        }
+    }
+
+    /**
+     * Shuts down the channel. This can be called multiple times for individual services to shutdown
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will shutdown all services <BR>
+     * MBR_RX_SEQ - starts the membership receiver <BR>
+     * MBR_TX_SEQ - starts the membership broadcaster <BR>
+     * SND_TX_SEQ - starts the replication transmitter<BR>
+     * SND_RX_SEQ - starts the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     */
+    protected synchronized void internalStop(int svc) throws ChannelException {
+        try {
+            //make sure we don't pass down any flags that are unrelated to the bottom layer
+            svc = svc & Channel.DEFAULT;
+
+            if (startLevel == 0) return; //we have already stopped up all components
+            if (svc == 0 ) return;//nothing to stop
+
+            boolean valid = false;
+            if ( Channel.SND_RX_SEQ==(svc & Channel.SND_RX_SEQ) ) {
+                clusterReceiver.stop();
+                clusterReceiver.setMessageListener(null);
+                valid = true;
+            }
+            if ( Channel.SND_TX_SEQ==(svc & Channel.SND_TX_SEQ) ) {
+                clusterSender.stop();
+                valid = true;
+            }
+
+            if ( Channel.MBR_RX_SEQ==(svc & Channel.MBR_RX_SEQ) ) {
+                membershipService.stop(MembershipService.MBR_RX);
+                membershipService.setMembershipListener(null);
+                valid = true;
+                
+            }
+            if ( Channel.MBR_TX_SEQ==(svc & Channel.MBR_TX_SEQ) ) {
+                valid = true;
+                membershipService.stop(MembershipService.MBR_TX);
+            }            
+            if ( !valid) {
+                throw new IllegalArgumentException("Invalid start level, valid levels are:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ");
+            }
+
+            startLevel = (startLevel & (~svc));
+            
+        }catch ( Exception x ) {
+            throw new ChannelException(x);
+        } finally {
+            
+        }
+
+    }
+    
+    @Override
+    public void memberAdded(Member member){
+        SenderState.getSenderState(member);
+        super.memberAdded(member);
+    }
+    
+    @Override
+    public void memberDisappeared(Member member){
+        SenderState.removeSenderState(member);
+        super.memberDisappeared(member);
+    }
+    
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        if ( Logs.MESSAGES.isTraceEnabled() ) {
+            Logs.MESSAGES.trace("ChannelCoordinator - Received msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " from "+msg.getAddress().getName());
+        }
+        super.messageReceived(msg);
+    }
+
+    public ChannelReceiver getClusterReceiver() {
+        return clusterReceiver;
+    }
+
+    public ChannelSender getClusterSender() {
+        return clusterSender;
+    }
+
+    public MembershipService getMembershipService() {
+        return membershipService;
+    }
+
+    public void setClusterReceiver(ChannelReceiver clusterReceiver) {
+        if ( clusterReceiver != null ) {
+            this.clusterReceiver = clusterReceiver;
+            this.clusterReceiver.setMessageListener(this);
+        } else {
+            if  (this.clusterReceiver!=null ) this.clusterReceiver.setMessageListener(null);
+            this.clusterReceiver = null;
+        }
+    }
+
+    public void setClusterSender(ChannelSender clusterSender) {
+        this.clusterSender = clusterSender;
+    }
+
+    public void setMembershipService(MembershipService membershipService) {
+        this.membershipService = membershipService;
+        this.membershipService.setMembershipListener(this);
+    }
+    
+    @Override
+    public void heartbeat() {
+        if ( clusterSender!=null ) clusterSender.heartbeat();
+        super.heartbeat();
+    }
+    
+    /**
+     * has members
+     */
+    @Override
+    public boolean hasMembers() {
+        return this.getMembershipService().hasMembers();
+    }
+
+    /**
+     * Get all current cluster members
+     * @return all members or empty array
+     */
+    @Override
+    public Member[] getMembers() {
+        return this.getMembershipService().getMembers();
+    }
+
+    /**
+     * 
+     * @param mbr Member
+     * @return Member
+     */
+    @Override
+    public Member getMember(Member mbr){
+        return this.getMembershipService().getMember(mbr);
+    }
+
+
+    /**
+     * Return the member that represents this node.
+     *
+     * @return Member
+     */
+    @Override
+    public Member getLocalMember(boolean incAlive) {
+        return this.getMembershipService().getLocalMember(incAlive);
+    }
+
+   
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ChannelInterceptorBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ChannelInterceptorBase.java
new file mode 100644
index 0000000..b46dc34
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ChannelInterceptorBase.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelInterceptor;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Abstract class for the interceptor base class.
+ * @author Filip Hanik
+ * @version $Id: ChannelInterceptorBase.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+
+public abstract class ChannelInterceptorBase implements ChannelInterceptor {
+
+    private static final Log log = LogFactory.getLog(ChannelInterceptorBase.class);
+
+    private ChannelInterceptor next;
+    private ChannelInterceptor previous;
+    //default value, always process
+    protected int optionFlag = 0;
+
+    public ChannelInterceptorBase() {
+
+    }
+    
+    public boolean okToProcess(int messageFlags) { 
+        if (this.optionFlag == 0 ) return true;
+        return ((optionFlag&messageFlags) == optionFlag);
+    }
+
+    public final void setNext(ChannelInterceptor next) {
+        this.next = next;
+    }
+
+    public final ChannelInterceptor getNext() {
+        return next;
+    }
+
+    public final void setPrevious(ChannelInterceptor previous) {
+        this.previous = previous;
+    }
+
+    public void setOptionFlag(int optionFlag) {
+        this.optionFlag = optionFlag;
+    }
+
+    public final ChannelInterceptor getPrevious() {
+        return previous;
+    }
+
+    public int getOptionFlag() {
+        return optionFlag;
+    }
+
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws
+        ChannelException {
+        if (getNext() != null) getNext().sendMessage(destination, msg, payload);
+    }
+
+    public void messageReceived(ChannelMessage msg) {
+        if (getPrevious() != null) getPrevious().messageReceived(msg);
+    }
+
+    public boolean accept(ChannelMessage msg) {
+        return true;
+    }
+
+    public void memberAdded(Member member) {
+        //notify upwards
+        if (getPrevious() != null) getPrevious().memberAdded(member);
+    }
+
+    public void memberDisappeared(Member member) {
+        //notify upwards
+        if (getPrevious() != null) getPrevious().memberDisappeared(member);
+    }
+
+    public void heartbeat() {
+        if (getNext() != null) getNext().heartbeat();
+    }
+
+    /**
+     * has members
+     */
+    public boolean hasMembers() {
+        if ( getNext()!=null )return getNext().hasMembers();
+        else return false;
+    }
+
+    /**
+     * Get all current cluster members
+     * @return all members or empty array
+     */
+    public Member[] getMembers() {
+        if ( getNext()!=null ) return getNext().getMembers();
+        else return null;
+    }
+
+    /**
+     *
+     * @param mbr Member
+     * @return Member
+     */
+    public Member getMember(Member mbr) {
+        if ( getNext()!=null) return getNext().getMember(mbr);
+        else return null;
+    }
+
+    /**
+     * Return the member that represents this node.
+     *
+     * @return Member
+     */
+    public Member getLocalMember(boolean incAlive) {
+        if ( getNext()!=null ) return getNext().getLocalMember(incAlive);
+        else return null;
+    }
+    
+    /**
+     * Starts up the channel. This can be called multiple times for individual services to start
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will start all services <BR>
+     * MBR_RX_SEQ - starts the membership receiver <BR>
+     * MBR_TX_SEQ - starts the membership broadcaster <BR>
+     * SND_TX_SEQ - starts the replication transmitter<BR>
+     * SND_RX_SEQ - starts the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     */
+    public void start(int svc) throws ChannelException {
+        if ( getNext()!=null ) getNext().start(svc);
+    }
+
+    /**
+     * Shuts down the channel. This can be called multiple times for individual services to shutdown
+     * The svc parameter can be the logical or value of any constants
+     * @param svc int value of <BR>
+     * DEFAULT - will shutdown all services <BR>
+     * MBR_RX_SEQ - stops the membership receiver <BR>
+     * MBR_TX_SEQ - stops the membership broadcaster <BR>
+     * SND_TX_SEQ - stops the replication transmitter<BR>
+     * SND_RX_SEQ - stops the replication receiver<BR>
+     * @throws ChannelException if a startup error occurs or the service is already started.
+     */
+    public void stop(int svc) throws ChannelException {
+        if (getNext() != null) getNext().stop(svc);
+    }
+    
+    public void fireInterceptorEvent(InterceptorEvent event) {
+        //empty operation
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ExtendedRpcCallback.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ExtendedRpcCallback.java
new file mode 100644
index 0000000..e24e484
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/ExtendedRpcCallback.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Member;
+/**
+ * Extension to the {@link RpcCallback} interface. Allows a RPC messenger to get a confirmation if the reply
+ * was sent successfully to the original sender. 
+ * @author fhanik
+ *
+ */
+public interface ExtendedRpcCallback extends RpcCallback {
+    
+    /**
+     * The reply failed. 
+     * @param request - the original message that requested the reply
+     * @param response - the reply message to the original message
+     * @param sender - the sender requested that reply
+     * @param reason - the reason the reply failed
+     */
+    public void replyFailed(Serializable request, Serializable response, Member sender, Exception reason);
+    
+    /**
+     * The reply succeeded 
+     * @param request - the original message that requested the reply
+     * @param response - the reply message to the original message
+     * @param sender - the sender requested that reply
+     */
+    public void replySucceeded(Serializable request, Serializable response, Member sender);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/GroupChannel.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/GroupChannel.java
new file mode 100644
index 0000000..180cadc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/GroupChannel.java
@@ -0,0 +1,684 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.catalina.tribes.ByteMessage;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelInterceptor;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.ChannelSender;
+import org.apache.catalina.tribes.ErrorHandler;
+import org.apache.catalina.tribes.Heartbeat;
+import org.apache.catalina.tribes.ManagedChannel;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.RemoteProcessException;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor;
+import org.apache.catalina.tribes.io.BufferPool;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.util.Logs;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * The default implementation of a Channel.<br>
+ * The GroupChannel manages the replication channel. It coordinates
+ * message being sent and received with membership announcements.
+ * The channel has an chain of interceptors that can modify the message or perform other logic.<br>
+ * It manages a complete group, both membership and replication.
+ * @author Filip Hanik
+ * @version $Id: GroupChannel.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public class GroupChannel extends ChannelInterceptorBase implements ManagedChannel {
+    private static final Log log = LogFactory.getLog(GroupChannel.class);
+
+    /**
+     * Flag to determine if the channel manages its own heartbeat
+     * If set to true, the channel will start a local thread for the heart beat.
+     */
+    protected boolean heartbeat = true;
+    /**
+     * If <code>heartbeat == true</code> then how often do we want this
+     * heartbeat to run. default is one minute
+     */
+    protected long heartbeatSleeptime = 5*1000;//every 5 seconds
+
+    /**
+     * Internal heartbeat thread
+     */
+    protected HeartbeatThread hbthread = null;
+
+    /**
+     * The  <code>ChannelCoordinator</code> coordinates the bottom layer components:<br>
+     * - MembershipService<br>
+     * - ChannelSender <br>
+     * - ChannelReceiver<br>
+     */
+    protected ChannelCoordinator coordinator = new ChannelCoordinator();
+
+    /**
+     * The first interceptor in the interceptor stack.
+     * The interceptors are chained in a linked list, so we only need a reference to the
+     * first one
+     */
+    protected ChannelInterceptor interceptors = null;
+
+    /**
+     * A list of membership listeners that subscribe to membership announcements
+     */
+    protected ArrayList<Object> membershipListeners = new ArrayList<Object>();
+
+    /**
+     * A list of channel listeners that subscribe to incoming messages
+     */
+    protected ArrayList<Object> channelListeners = new ArrayList<Object>();
+
+    /**
+     * If set to true, the GroupChannel will check to make sure that
+     */
+    protected boolean optionCheck = false;
+
+    /**
+     * Creates a GroupChannel. This constructor will also
+     * add the first interceptor in the GroupChannel.<br>
+     * The first interceptor is always the channel itself.
+     */
+    public GroupChannel() {
+        addInterceptor(this);
+    }
+
+
+    /**
+     * Adds an interceptor to the stack for message processing<br>
+     * Interceptors are ordered in the way they are added.<br>
+     * <code>channel.addInterceptor(A);</code><br>
+     * <code>channel.addInterceptor(C);</code><br>
+     * <code>channel.addInterceptor(B);</code><br>
+     * Will result in a interceptor stack like this:<br>
+     * <code>A -> C -> B</code><br>
+     * The complete stack will look like this:<br>
+     * <code>Channel -> A -> C -> B -> ChannelCoordinator</code><br>
+     * @param interceptor ChannelInterceptorBase
+     */
+    public void addInterceptor(ChannelInterceptor interceptor) {
+        if ( interceptors == null ) {
+            interceptors = interceptor;
+            interceptors.setNext(coordinator);
+            interceptors.setPrevious(null);
+            coordinator.setPrevious(interceptors);
+        } else {
+            ChannelInterceptor last = interceptors;
+            while ( last.getNext() != coordinator ) {
+                last = last.getNext();
+            }
+            last.setNext(interceptor);
+            interceptor.setNext(coordinator);
+            interceptor.setPrevious(last);
+            coordinator.setPrevious(interceptor);
+        }
+    }
+
+    /**
+     * Sends a heartbeat through the interceptor stack.<br>
+     * Invoke this method from the application on a periodic basis if
+     * you have turned off internal heartbeats <code>channel.setHeartbeat(false)</code>
+     */
+    @Override
+    public void heartbeat() {
+        super.heartbeat();
+        Iterator<Object> i = membershipListeners.iterator();
+        while ( i.hasNext() ) {
+            Object o = i.next();
+            if ( o instanceof Heartbeat ) ((Heartbeat)o).heartbeat();
+        }
+        i = channelListeners.iterator();
+        while ( i.hasNext() ) {
+            Object o = i.next();
+            if ( o instanceof Heartbeat ) ((Heartbeat)o).heartbeat();
+        }
+
+    }
+
+
+    /**
+     * Send a message to the destinations specified
+     * @param destination Member[] - destination.length > 1
+     * @param msg Serializable - the message to send
+     * @param options int - sender options, options can trigger guarantee levels and different interceptors to
+     * react to the message see class documentation for the <code>Channel</code> object.<br>
+     * @return UniqueId - the unique Id that was assigned to this message
+     * @throws ChannelException - if an error occurs processing the message
+     * @see org.apache.catalina.tribes.Channel
+     */
+    public UniqueId send(Member[] destination, Serializable msg, int options) throws ChannelException {
+        return send(destination,msg,options,null);
+    }
+
+    /**
+     *
+     * @param destination Member[] - destination.length > 1
+     * @param msg Serializable - the message to send
+     * @param options int - sender options, options can trigger guarantee levels and different interceptors to
+     * react to the message see class documentation for the <code>Channel</code> object.<br>
+     * @param handler - callback object for error handling and completion notification, used when a message is
+     * sent asynchronously using the <code>Channel.SEND_OPTIONS_ASYNCHRONOUS</code> flag enabled.
+     * @return UniqueId - the unique Id that was assigned to this message
+     * @throws ChannelException - if an error occurs processing the message
+     * @see org.apache.catalina.tribes.Channel
+     */
+    public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler) throws ChannelException {
+        if ( msg == null ) throw new ChannelException("Cant send a NULL message");
+        XByteBuffer buffer = null;
+        try {
+            if ( destination == null || destination.length == 0) throw new ChannelException("No destination given");
+            ChannelData data = new ChannelData(true);//generates a unique Id
+            data.setAddress(getLocalMember(false));
+            data.setTimestamp(System.currentTimeMillis());
+            byte[] b = null;
+            if ( msg instanceof ByteMessage ){
+                b = ((ByteMessage)msg).getMessage();
+                options = options | SEND_OPTIONS_BYTE_MESSAGE;
+            } else {
+                b = XByteBuffer.serialize(msg);
+                options = options & (~SEND_OPTIONS_BYTE_MESSAGE);
+            }
+            data.setOptions(options);
+            //XByteBuffer buffer = new XByteBuffer(b.length+128,false);
+            buffer = BufferPool.getBufferPool().getBuffer(b.length+128, false);
+            buffer.append(b,0,b.length);
+            data.setMessage(buffer);
+            InterceptorPayload payload = null;
+            if ( handler != null ) {
+                payload = new InterceptorPayload();
+                payload.setErrorHandler(handler);
+            }
+            getFirstInterceptor().sendMessage(destination, data, payload);
+            if ( Logs.MESSAGES.isTraceEnabled() ) {
+                Logs.MESSAGES.trace("GroupChannel - Sent msg:" + new UniqueId(data.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+Arrays.toNameString(destination));
+                Logs.MESSAGES.trace("GroupChannel - Send Message:" + new UniqueId(data.getUniqueId()) + " is " +msg);
+            }
+
+            return new UniqueId(data.getUniqueId());
+        }catch ( Exception x ) {
+            if ( x instanceof ChannelException ) throw (ChannelException)x;
+            throw new ChannelException(x);
+        } finally {
+            if ( buffer != null ) BufferPool.getBufferPool().returnBuffer(buffer);
+        }
+    }
+
+
+    /**
+     * Callback from the interceptor stack. <br>
+     * When a message is received from a remote node, this method will be invoked by
+     * the previous interceptor.<br>
+     * This method can also be used to send a message to other components within the same application,
+     * but its an extreme case, and you're probably better off doing that logic between the applications itself.
+     * @param msg ChannelMessage
+     */
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        if ( msg == null ) return;
+        try {
+            if ( Logs.MESSAGES.isTraceEnabled() ) {
+                Logs.MESSAGES.trace("GroupChannel - Received msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " from "+msg.getAddress().getName());
+            }
+
+            Serializable fwd = null;
+            if ( (msg.getOptions() & SEND_OPTIONS_BYTE_MESSAGE) == SEND_OPTIONS_BYTE_MESSAGE ) {
+                fwd = new ByteMessage(msg.getMessage().getBytes());
+            } else {
+                try {
+                    fwd = XByteBuffer.deserialize(msg.getMessage().getBytesDirect(), 0, msg.getMessage().getLength());
+                }catch (Exception sx) {
+                    log.error("Unable to deserialize message:"+msg,sx);
+                    return;
+                }
+            }
+            if ( Logs.MESSAGES.isTraceEnabled() ) {
+                Logs.MESSAGES.trace("GroupChannel - Receive Message:" + new UniqueId(msg.getUniqueId()) + " is " +fwd);
+            }
+
+            //get the actual member with the correct alive time
+            Member source = msg.getAddress();
+            boolean rx = false;
+            boolean delivered = false;
+            for ( int i=0; i<channelListeners.size(); i++ ) {
+                ChannelListener channelListener = (ChannelListener)channelListeners.get(i);
+                if (channelListener != null && channelListener.accept(fwd, source)) {
+                    channelListener.messageReceived(fwd, source);
+                    delivered = true;
+                    //if the message was accepted by an RPC channel, that channel
+                    //is responsible for returning the reply, otherwise we send an absence reply
+                    if ( channelListener instanceof RpcChannel ) rx = true;
+                }
+            }//for
+            if ((!rx) && (fwd instanceof RpcMessage)) {
+                //if we have a message that requires a response,
+                //but none was given, send back an immediate one
+                sendNoRpcChannelReply((RpcMessage)fwd,source);
+            }
+            if ( Logs.MESSAGES.isTraceEnabled() ) {
+                Logs.MESSAGES.trace("GroupChannel delivered["+delivered+"] id:"+new UniqueId(msg.getUniqueId()));
+            }
+
+        } catch ( Exception x ) {
+            //this could be the channel listener throwing an exception, we should log it 
+            //as a warning.
+            if ( log.isWarnEnabled() ) log.warn("Error receiving message:",x);
+            throw new RemoteProcessException("Exception:"+x.getMessage(),x);
+        }
+    }
+
+    /**
+     * Sends a <code>NoRpcChannelReply</code> message to a member<br>
+     * This method gets invoked by the channel if a RPC message comes in
+     * and no channel listener accepts the message. This avoids timeout
+     * @param msg RpcMessage
+     * @param destination Member - the destination for the reply
+     */
+    protected void sendNoRpcChannelReply(RpcMessage msg, Member destination) {
+        try {
+            //avoid circular loop
+            if ( msg instanceof RpcMessage.NoRpcChannelReply) return;
+            RpcMessage.NoRpcChannelReply reply = new RpcMessage.NoRpcChannelReply(msg.rpcId,msg.uuid);
+            send(new Member[]{destination},reply,Channel.SEND_OPTIONS_ASYNCHRONOUS);
+        } catch ( Exception x ) {
+            log.error("Unable to find rpc channel, failed to send NoRpcChannelReply.",x);
+        }
+    }
+
+    /**
+     * memberAdded gets invoked by the interceptor below the channel
+     * and the channel will broadcast it to the membership listeners
+     * @param member Member - the new member
+     */
+    @Override
+    public void memberAdded(Member member) {
+        //notify upwards
+        for (int i=0; i<membershipListeners.size(); i++ ) {
+            MembershipListener membershipListener = (MembershipListener)membershipListeners.get(i);
+            if (membershipListener != null) membershipListener.memberAdded(member);
+        }
+    }
+
+    /**
+     * memberDisappeared gets invoked by the interceptor below the channel
+     * and the channel will broadcast it to the membership listeners
+     * @param member Member - the member that left or crashed
+     */
+    @Override
+    public void memberDisappeared(Member member) {
+        //notify upwards
+        for (int i=0; i<membershipListeners.size(); i++ ) {
+            MembershipListener membershipListener = (MembershipListener)membershipListeners.get(i);
+            if (membershipListener != null) membershipListener.memberDisappeared(member);
+        }
+    }
+
+    /**
+     * Sets up the default implementation interceptor stack
+     * if no interceptors have been added
+     * @throws ChannelException
+     */
+    protected synchronized void setupDefaultStack() throws ChannelException {
+
+        if ( getFirstInterceptor() != null &&
+             ((getFirstInterceptor().getNext() instanceof ChannelCoordinator))) {
+            ChannelInterceptor interceptor = null;
+            Class<?> clazz = null;
+            try {
+                clazz = Class.forName("org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor",
+                                      true,GroupChannel.class.getClassLoader());
+                clazz.newInstance();
+            } catch ( Throwable x ) {
+                clazz = MessageDispatchInterceptor.class;
+            }//catch
+            try {
+                interceptor = (ChannelInterceptor) clazz.newInstance();
+            } catch (Exception x) {
+                throw new ChannelException("Unable to add MessageDispatchInterceptor to interceptor chain.",x);
+            }
+            this.addInterceptor(interceptor);
+        }
+    }
+
+    /**
+     * Validates the option flags that each interceptor is using and reports
+     * an error if two interceptor share the same flag.
+     * @throws ChannelException
+     */
+    protected void checkOptionFlags() throws ChannelException {
+        StringBuilder conflicts = new StringBuilder();
+        ChannelInterceptor first = interceptors;
+        while ( first != null ) {
+            int flag = first.getOptionFlag();
+            if ( flag != 0 ) {
+                ChannelInterceptor next = first.getNext();
+                while ( next != null ) {
+                    int nflag = next.getOptionFlag();
+                    if (nflag!=0 && (((flag & nflag) == flag ) || ((flag & nflag) == nflag)) ) {
+                        conflicts.append("[");
+                        conflicts.append(first.getClass().getName());
+                        conflicts.append(":");
+                        conflicts.append(flag);
+                        conflicts.append(" == ");
+                        conflicts.append(next.getClass().getName());
+                        conflicts.append(":");
+                        conflicts.append(nflag);
+                        conflicts.append("] ");
+                    }//end if
+                    next = next.getNext();
+                }//while
+            }//end if
+            first = first.getNext();
+        }//while
+        if ( conflicts.length() > 0 ) throw new ChannelException("Interceptor option flag conflict: "+conflicts.toString());
+
+    }
+
+    /**
+     * Starts the channel
+     * @param svc int - what service to start
+     * @throws ChannelException
+     * @see org.apache.catalina.tribes.Channel#start(int)
+     */
+    @Override
+    public synchronized void start(int svc) throws ChannelException {
+        setupDefaultStack();
+        if (optionCheck) checkOptionFlags();
+        super.start(svc);
+        if ( hbthread == null && heartbeat ) {
+            hbthread = new HeartbeatThread(this,heartbeatSleeptime);
+            hbthread.start();
+        }
+    }
+
+    /**
+     * Stops the channel
+     * @param svc int
+     * @throws ChannelException
+     * @see org.apache.catalina.tribes.Channel#stop(int)
+     */
+    @Override
+    public synchronized void stop(int svc) throws ChannelException {
+        if (hbthread != null) {
+            hbthread.stopHeartbeat();
+            hbthread = null;
+        }
+        super.stop(svc);
+    }
+
+    /**
+     * Returns the first interceptor of the stack. Useful for traversal.
+     * @return ChannelInterceptor
+     */
+    public ChannelInterceptor getFirstInterceptor() {
+        if (interceptors != null) return interceptors;
+        else return coordinator;
+    }
+
+    /**
+     * Returns the channel receiver component
+     * @return ChannelReceiver
+     */
+    public ChannelReceiver getChannelReceiver() {
+        return coordinator.getClusterReceiver();
+    }
+
+    /**
+     * Returns the channel sender component
+     * @return ChannelSender
+     */
+    public ChannelSender getChannelSender() {
+        return coordinator.getClusterSender();
+    }
+
+    /**
+     * Returns the membership service component
+     * @return MembershipService
+     */
+    public MembershipService getMembershipService() {
+        return coordinator.getMembershipService();
+    }
+
+    /**
+     * Sets the channel receiver component
+     * @param clusterReceiver ChannelReceiver
+     */
+    public void setChannelReceiver(ChannelReceiver clusterReceiver) {
+        coordinator.setClusterReceiver(clusterReceiver);
+    }
+
+    /**
+     * Sets the channel sender component
+     * @param clusterSender ChannelSender
+     */
+    public void setChannelSender(ChannelSender clusterSender) {
+        coordinator.setClusterSender(clusterSender);
+    }
+
+    /**
+     * Sets the membership component
+     * @param membershipService MembershipService
+     */
+    public void setMembershipService(MembershipService membershipService) {
+        coordinator.setMembershipService(membershipService);
+    }
+
+    /**
+     * Adds a membership listener to the channel.<br>
+     * Membership listeners are uniquely identified using the equals(Object) method
+     * @param membershipListener MembershipListener
+     */
+    public void addMembershipListener(MembershipListener membershipListener) {
+        if (!this.membershipListeners.contains(membershipListener) )
+            this.membershipListeners.add(membershipListener);
+    }
+
+    /**
+     * Removes a membership listener from the channel.<br>
+     * Membership listeners are uniquely identified using the equals(Object) method
+     * @param membershipListener MembershipListener
+     */
+
+    public void removeMembershipListener(MembershipListener membershipListener) {
+        membershipListeners.remove(membershipListener);
+    }
+
+    /**
+     * Adds a channel listener to the channel.<br>
+     * Channel listeners are uniquely identified using the equals(Object) method
+     * @param channelListener ChannelListener
+     */
+    public void addChannelListener(ChannelListener channelListener) {
+        if (!this.channelListeners.contains(channelListener) ) {
+            this.channelListeners.add(channelListener);
+        } else {
+            throw new IllegalArgumentException("Listener already exists:"+channelListener+"["+channelListener.getClass().getName()+"]");
+        }
+    }
+
+    /**
+     *
+     * Removes a channel listener from the channel.<br>
+     * Channel listeners are uniquely identified using the equals(Object) method
+     * @param channelListener ChannelListener
+     */
+    public void removeChannelListener(ChannelListener channelListener) {
+        channelListeners.remove(channelListener);
+    }
+
+    /**
+     * Returns an iterator of all the interceptors in this stack
+     * @return Iterator
+     */
+    public Iterator<ChannelInterceptor> getInterceptors() {
+        return new InterceptorIterator(this.getNext(),this.coordinator);
+    }
+
+    /**
+     * Enables/disables the option check<br>
+     * Setting this to true, will make the GroupChannel perform a conflict check
+     * on the interceptors. If two interceptors are using the same option flag
+     * and throw an error upon start.
+     * @param optionCheck boolean
+     */
+    public void setOptionCheck(boolean optionCheck) {
+        this.optionCheck = optionCheck;
+    }
+
+    /**
+     * Configure local heartbeat sleep time<br>
+     * Only used when <code>getHeartbeat()==true</code>
+     * @param heartbeatSleeptime long - time in milliseconds to sleep between heartbeats
+     */
+    public void setHeartbeatSleeptime(long heartbeatSleeptime) {
+        this.heartbeatSleeptime = heartbeatSleeptime;
+    }
+
+    /**
+     * Enables or disables local heartbeat.
+     * if <code>setHeartbeat(true)</code> is invoked then the channel will start an internal
+     * thread to invoke <code>Channel.heartbeat()</code> every <code>getHeartbeatSleeptime</code> milliseconds
+     * @param heartbeat boolean
+     */
+    public void setHeartbeat(boolean heartbeat) {
+        this.heartbeat = heartbeat;
+    }
+
+    /**
+     * @see #setOptionCheck(boolean)
+     * @return boolean
+     */
+    public boolean getOptionCheck() {
+        return optionCheck;
+    }
+
+    /**
+     * @see #setHeartbeat(boolean)
+     * @return boolean
+     */
+    public boolean getHeartbeat() {
+        return heartbeat;
+    }
+
+    /**
+     * Returns the sleep time in milliseconds that the internal heartbeat will
+     * sleep in between invocations of <code>Channel.heartbeat()</code>
+     * @return long
+     */
+    public long getHeartbeatSleeptime() {
+        return heartbeatSleeptime;
+    }
+
+    /**
+     *
+     * <p>Title: Interceptor Iterator</p>
+     *
+     * <p>Description: An iterator to loop through the interceptors in a channel</p>
+     *
+     * @version 1.0
+     */
+    public static class InterceptorIterator implements Iterator<ChannelInterceptor> {
+        private ChannelInterceptor end;
+        private ChannelInterceptor start;
+        public InterceptorIterator(ChannelInterceptor start, ChannelInterceptor end) {
+            this.end = end;
+            this.start = start;
+        }
+
+        public boolean hasNext() {
+            return start!=null && start != end;
+        }
+
+        public ChannelInterceptor next() {
+            ChannelInterceptor result = null;
+            if ( hasNext() ) {
+                result = start;
+                start = start.getNext();
+            }
+            return result;
+        }
+
+        public void remove() {
+            //empty operation
+        }
+    }
+
+    /**
+     *
+     * <p>Title: Internal heartbeat thread</p>
+     *
+     * <p>Description: if <code>Channel.getHeartbeat()==true</code> then a thread of this class
+     * is created</p>
+     *
+     * @version 1.0
+     */
+    public static class HeartbeatThread extends Thread {
+        private static final Log log = LogFactory.getLog(HeartbeatThread.class);
+        protected static int counter = 1;
+        protected static synchronized int inc() {
+            return counter++;
+        }
+
+        protected volatile boolean doRun = true;
+        protected GroupChannel channel;
+        protected long sleepTime;
+        public HeartbeatThread(GroupChannel channel, long sleepTime) {
+            super();
+            this.setPriority(MIN_PRIORITY);
+            setName("GroupChannel-Heartbeat-"+inc());
+            setDaemon(true);
+            this.channel = channel;
+            this.sleepTime = sleepTime;
+        }
+        public void stopHeartbeat() {
+            doRun = false;
+            interrupt();
+        }
+
+        @Override
+        public void run() {
+            while (doRun) {
+                try {
+                    Thread.sleep(sleepTime);
+                    channel.heartbeat();
+                } catch ( InterruptedException x ) {
+                    interrupted();
+                } catch ( Exception x ) {
+                    log.error("Unable to send heartbeat through Tribes interceptor stack. Will try to sleep again.",x);
+                }//catch
+            }//while
+        }//run
+    }//HeartbeatThread
+
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/InterceptorPayload.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/InterceptorPayload.java
new file mode 100644
index 0000000..27ef3e8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/InterceptorPayload.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import org.apache.catalina.tribes.ErrorHandler;
+
+/**
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class InterceptorPayload  {
+    private ErrorHandler errorHandler;
+    
+    public ErrorHandler getErrorHandler() {
+        return errorHandler;
+    }
+
+    public void setErrorHandler(ErrorHandler errorHandler) {
+        this.errorHandler = errorHandler;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/Response.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/Response.java
new file mode 100644
index 0000000..16e01a8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/Response.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * A response object holds a message from a responding partner.
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class Response {
+    private Member source;
+    private Serializable message;
+    public Response() {
+    }
+    
+    public Response(Member source, Serializable message) {
+        this.source = source;
+        this.message = message;
+    }
+
+    public void setSource(Member source) {
+        this.source = source;
+    }
+
+    public void setMessage(Serializable message) {
+        this.message = message;
+    }
+
+    public Member getSource() {
+        return source;
+    }
+
+    public Serializable getMessage() {
+        return message;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcCallback.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcCallback.java
new file mode 100644
index 0000000..93140db
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcCallback.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * The RpcCallback interface is an interface for the Tribes channel to request a
+ * response object to a request that came in.
+ * @author not attributable
+ * @version 1.0
+ */
+public interface RpcCallback {
+    
+    /**
+     * 
+     * @param msg Serializable
+     * @return Serializable - null if no reply should be sent
+     */
+    public Serializable replyRequest(Serializable msg, Member sender);
+    
+    /**
+     * If the reply has already been sent to the requesting thread,
+     * the rpc callback can handle any data that comes in after the fact.
+     * @param msg Serializable
+     * @param sender Member
+     */
+    public void leftOver(Serializable msg, Member sender);
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcChannel.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcChannel.java
new file mode 100644
index 0000000..39c4c04
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcChannel.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.ErrorHandler;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * A channel to handle RPC messaging
+ * @author Filip Hanik
+ */
+public class RpcChannel implements ChannelListener{
+    private static final Log log = LogFactory.getLog(RpcChannel.class);
+    
+    public static final int FIRST_REPLY = 1;
+    public static final int MAJORITY_REPLY = 2;
+    public static final int ALL_REPLY = 3;
+    public static final int NO_REPLY = 4;
+    
+    private Channel channel;
+    private RpcCallback callback;
+    private byte[] rpcId;
+    private int replyMessageOptions = 0;
+
+    private HashMap<RpcCollectorKey, RpcCollector> responseMap = new HashMap<RpcCollectorKey, RpcCollector>();
+
+    /**
+     * Create an RPC channel. You can have several RPC channels attached to a group
+     * all separated out by the uniqueness
+     * @param rpcId - the unique Id for this RPC group
+     * @param channel Channel
+     * @param callback RpcCallback
+     */
+    public RpcChannel(byte[] rpcId, Channel channel, RpcCallback callback) {
+        this.channel = channel;
+        this.callback = callback;
+        this.rpcId = rpcId;
+        channel.addChannelListener(this);
+    }
+    
+    
+    /**
+     * Send a message and wait for the response.
+     * @param destination Member[] - the destination for the message, and the members you request a reply from
+     * @param message Serializable - the message you are sending out
+     * @param rpcOptions int - FIRST_REPLY, MAJORITY_REPLY or ALL_REPLY
+     * @param channelOptions channel sender options
+     * @param timeout long - timeout in milliseconds, if no reply is received within this time null is returned
+     * @return Response[] - an array of response objects.
+     * @throws ChannelException
+     */
+    public Response[] send(Member[] destination, 
+                           Serializable message,
+                           int rpcOptions, 
+                           int channelOptions,
+                           long timeout) throws ChannelException {
+        
+        if ( destination==null || destination.length == 0 ) return new Response[0];
+        
+        //avoid dead lock
+        int sendOptions =
+            channelOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
+        
+        RpcCollectorKey key = new RpcCollectorKey(UUIDGenerator.randomUUID(false));
+        RpcCollector collector = new RpcCollector(key,rpcOptions,destination.length,timeout);
+        try {
+            synchronized (collector) {
+                if ( rpcOptions != NO_REPLY ) responseMap.put(key, collector);
+                RpcMessage rmsg = new RpcMessage(rpcId, key.id, message);
+                channel.send(destination, rmsg, sendOptions);
+                if ( rpcOptions != NO_REPLY ) collector.wait(timeout);
+            }
+        } catch ( InterruptedException ix ) {
+            Thread.currentThread().interrupt();
+        }finally {
+            responseMap.remove(key);
+        }
+        return collector.getResponses();
+    }
+    
+    @Override
+    public void messageReceived(Serializable msg, Member sender) {
+        RpcMessage rmsg = (RpcMessage)msg;
+        RpcCollectorKey key = new RpcCollectorKey(rmsg.uuid);
+        if ( rmsg.reply ) {
+            RpcCollector collector = responseMap.get(key);
+            if (collector == null) {
+                callback.leftOver(rmsg.message, sender);
+            } else {
+                synchronized (collector) {
+                    //make sure it hasn't been removed
+                    if ( responseMap.containsKey(key) ) {
+                        if ( (rmsg instanceof RpcMessage.NoRpcChannelReply) ) 
+                            collector.destcnt--;
+                        else 
+                            collector.addResponse(rmsg.message, sender);
+                        if (collector.isComplete()) collector.notifyAll();
+                    } else {
+                        if (! (rmsg instanceof RpcMessage.NoRpcChannelReply) ) 
+                            callback.leftOver(rmsg.message, sender);
+                    }
+                }//synchronized
+            }//end if
+        } else{
+            boolean finished = false;
+            final ExtendedRpcCallback excallback = (callback instanceof ExtendedRpcCallback)?((ExtendedRpcCallback)callback) : null;
+            boolean asyncReply = ((replyMessageOptions & Channel.SEND_OPTIONS_ASYNCHRONOUS) == Channel.SEND_OPTIONS_ASYNCHRONOUS);
+            Serializable reply = callback.replyRequest(rmsg.message,sender);
+            ErrorHandler handler = null;
+            final Serializable request = msg;
+            final Serializable response = reply;
+            final Member fsender = sender;
+            if (excallback!=null && asyncReply) {
+                handler = new ErrorHandler() {
+                    @Override
+                    public void handleError(ChannelException x, UniqueId id) {
+                        excallback.replyFailed(request, response, fsender, x);
+                    }
+                    @Override
+                    public void handleCompletion(UniqueId id) {
+                        excallback.replySucceeded(request, response, fsender);
+                    }
+                };
+            }
+            rmsg.reply = true;
+            rmsg.message = reply;
+            try {
+                if (handler!=null) {
+                    channel.send(new Member[] {sender}, rmsg,replyMessageOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK, handler);
+                } else {
+                    channel.send(new Member[] {sender}, rmsg,replyMessageOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
+                }
+                finished = true;
+            }catch ( Exception x )  {
+                if (excallback != null && !asyncReply) {
+                    excallback.replyFailed(rmsg.message, reply, sender, x);
+                } else {
+                    log.error("Unable to send back reply in RpcChannel.",x);
+                }
+            }
+            if (finished && excallback != null && !asyncReply) {
+                excallback.replySucceeded(rmsg.message, reply, sender);
+            }
+        }//end if
+    }
+    
+    public void breakdown() {
+        channel.removeChannelListener(this);
+    }
+    
+    @Override
+    public void finalize() {
+        breakdown();
+    }
+    
+    @Override
+    public boolean accept(Serializable msg, Member sender) {
+        if ( msg instanceof RpcMessage ) {
+            RpcMessage rmsg = (RpcMessage)msg;
+            return Arrays.equals(rmsg.rpcId,rpcId);
+        }else return false;
+    }
+    
+    public Channel getChannel() {
+        return channel;
+    }
+
+    public RpcCallback getCallback() {
+        return callback;
+    }
+
+    public byte[] getRpcId() {
+        return rpcId;
+    }
+
+    public void setChannel(Channel channel) {
+        this.channel = channel;
+    }
+
+    public void setCallback(RpcCallback callback) {
+        this.callback = callback;
+    }
+
+    public void setRpcId(byte[] rpcId) {
+        this.rpcId = rpcId;
+    }
+
+    public int getReplyMessageOptions() {
+        return replyMessageOptions;
+    }
+
+    public void setReplyMessageOptions(int replyMessageOptions) {
+        this.replyMessageOptions = replyMessageOptions;
+    }
+        
+    /**
+     * 
+     * Class that holds all response.
+     * @author not attributable
+     * @version 1.0
+     */
+    public static class RpcCollector {
+        public ArrayList<Response> responses = new ArrayList<Response>(); 
+        public RpcCollectorKey key;
+        public int options;
+        public int destcnt;
+        public long timeout;
+        
+        public RpcCollector(RpcCollectorKey key, int options, int destcnt, long timeout) {
+            this.key = key;
+            this.options = options;
+            this.destcnt = destcnt;
+            this.timeout = timeout;
+        }
+        
+        public void addResponse(Serializable message, Member sender){
+            Response resp = new Response(sender,message);
+            responses.add(resp);
+        }
+        
+        public boolean isComplete() {
+            if ( destcnt <= 0 ) return true;
+            switch (options) {
+                case ALL_REPLY:
+                    return destcnt == responses.size();
+                case MAJORITY_REPLY:
+                {
+                    float perc = ((float)responses.size()) / ((float)destcnt);
+                    return perc >= 0.50f;
+                }
+                case FIRST_REPLY:
+                    return responses.size()>0;
+                default:
+                    return false;
+            }
+        }
+        
+        @Override
+        public int hashCode() {
+            return key.hashCode();
+        }
+        
+        @Override
+        public boolean equals(Object o) {
+            if ( o instanceof RpcCollector ) {
+                RpcCollector r = (RpcCollector)o;
+                return r.key.equals(this.key);
+            } else return false;
+        }
+        
+        public Response[] getResponses() {
+            return responses.toArray(new Response[responses.size()]);
+        }
+    }
+    
+    public static class RpcCollectorKey {
+        byte[] id;
+        public RpcCollectorKey(byte[] id) {
+            this.id = id;
+        }
+        
+        @Override
+        public int hashCode() {
+            return id[0]+id[1]+id[2]+id[3];
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if ( o instanceof RpcCollectorKey ) {
+                RpcCollectorKey r = (RpcCollectorKey)o;
+                return Arrays.equals(id,r.id);
+            } else return false;
+        }
+        
+    }
+    
+    protected static String bToS(byte[] data) {
+        StringBuilder buf = new StringBuilder(4*16);
+        buf.append("{");
+        for (int i=0; data!=null && i<data.length; i++ ) buf.append(String.valueOf(data[i])).append(" ");
+        buf.append("}");
+        return buf.toString();
+    }
+
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcMessage.java
new file mode 100644
index 0000000..6c6d6f8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/RpcMessage.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.group;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.util.Arrays;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class RpcMessage implements Externalizable {
+
+    protected Serializable message;
+    protected byte[] uuid;
+    protected byte[] rpcId;
+    protected boolean reply = false;
+
+    public RpcMessage() {
+        //for serialization
+    }
+
+    public RpcMessage(byte[] rpcId, byte[] uuid, Serializable message) {
+        this.rpcId = rpcId;
+        this.uuid = uuid;
+        this.message = message;
+    }
+
+    public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
+        reply = in.readBoolean();
+        int length = in.readInt();
+        uuid = new byte[length];
+        in.readFully(uuid);
+        length = in.readInt();
+        rpcId = new byte[length];
+        in.readFully(rpcId);
+        message = (Serializable)in.readObject();
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeBoolean(reply);
+        out.writeInt(uuid.length);
+        out.write(uuid, 0, uuid.length);
+        out.writeInt(rpcId.length);
+        out.write(rpcId, 0, rpcId.length);
+        out.writeObject(message);
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder("RpcMessage[");
+        buf.append(super.toString());
+        buf.append("] rpcId=");
+        buf.append(Arrays.toString(rpcId));
+        buf.append("; uuid=");
+        buf.append(Arrays.toString(uuid));
+        buf.append("; msg=");
+        buf.append(message);
+        return buf.toString();
+    }
+    
+    public static class NoRpcChannelReply extends RpcMessage {
+        public NoRpcChannelReply() {
+            
+        }
+
+        public NoRpcChannelReply(byte[] rpcid, byte[] uuid) {
+            super(rpcid,uuid,null);
+            reply = true;
+        }
+
+        @Override
+        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            reply = true;
+            int length = in.readInt();
+            uuid = new byte[length];
+            in.readFully(uuid);
+            length = in.readInt();
+            rpcId = new byte[length];
+            in.readFully(rpcId);
+        }
+
+        @Override
+        public void writeExternal(ObjectOutput out) throws IOException {
+            out.writeInt(uuid.length);
+            out.write(uuid, 0, uuid.length);
+            out.writeInt(rpcId.length);
+            out.write(rpcId, 0, rpcId.length);
+        }
+    }    
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/Constants.java
new file mode 100644
index 0000000..4fe4d3c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.tribes.membership;
+
+import org.apache.catalina.tribes.util.Arrays;
+
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.tribes.membership</code>
+ * package.
+ *
+ * @author Peter Rossbach
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ * @author Filip Hanik
+ */
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.tribes.membership";
+    public static void main(String[] args) throws Exception {
+        System.out.println(Arrays.toString("TRIBES-B".getBytes()));
+        System.out.println(Arrays.toString("TRIBES-E".getBytes()));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/LocalStrings.properties
new file mode 100644
index 0000000..e9bab46
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/LocalStrings.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cluster.mbean.register.already=MBean {0} already registered!
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/LocalStrings_es.properties
new file mode 100644
index 0000000..1a71cc4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/LocalStrings_es.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cluster.mbean.register.already = \u00A1El MBean {0} ya est\u00E1 registrado\!\!
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/McastService.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/McastService.java
new file mode 100644
index 0000000..2f5901e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/McastService.java
@@ -0,0 +1,683 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.util.Properties;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.MembershipService;
+import org.apache.catalina.tribes.MessageListener;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+
+/**
+ * A <b>membership</b> implementation using simple multicast.
+ * This is the representation of a multicast membership service.
+ * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+ * If a node fails to send out a heartbeat, the node will be dismissed.
+ *
+ * @author Filip Hanik
+ * @version $Id: McastService.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+
+
+public class McastService implements MembershipService,MembershipListener,MessageListener {
+
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( McastService.class );
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "McastService/2.1";
+
+    /**
+     * The implementation specific properties
+     */
+    protected Properties properties = new Properties();
+    /**
+     * A handle to the actual low level implementation
+     */
+    protected McastServiceImpl impl;
+    /**
+     * A membership listener delegate (should be the cluster :)
+     */
+    protected MembershipListener listener;
+    /**
+     * A message listener delegate for broadcasts
+     */
+    protected MessageListener msglistener;
+    /**
+     * The local member
+     */
+    protected MemberImpl localMember ;
+    private int mcastSoTimeout;
+    private int mcastTTL;
+
+    protected byte[] payload;
+
+    protected byte[] domain;
+
+    /**
+     * Create a membership service.
+     */
+    public McastService() {
+        //default values
+        properties.setProperty("mcastPort","45564");
+        properties.setProperty("mcastAddress","228.0.0.4");
+        properties.setProperty("memberDropTime","3000");
+        properties.setProperty("mcastFrequency","500");
+
+    }
+
+    /**
+     * Return descriptive information about this implementation and the
+     * corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo() {
+        return (info);
+    }
+
+    /**
+     *
+     * @param properties
+     * <BR/>All are required<BR />
+     * 1. mcastPort - the port to listen to<BR>
+     * 2. mcastAddress - the mcast group address<BR>
+     * 4. bindAddress - the bind address if any - only one that can be null<BR>
+     * 5. memberDropTime - the time a member is gone before it is considered gone.<BR>
+     * 6. mcastFrequency - the frequency of sending messages<BR>
+     * 7. tcpListenPort - the port this member listens to<BR>
+     * 8. tcpListenHost - the bind address of this member<BR>
+     * @exception java.lang.IllegalArgumentException if a property is missing.
+     */
+    public void setProperties(Properties properties) {
+        hasProperty(properties,"mcastPort");
+        hasProperty(properties,"mcastAddress");
+        hasProperty(properties,"memberDropTime");
+        hasProperty(properties,"mcastFrequency");
+        hasProperty(properties,"tcpListenPort");
+        hasProperty(properties,"tcpListenHost");
+        this.properties = properties;
+    }
+
+    /**
+     * Return the properties, see setProperties
+     */
+    public Properties getProperties() {
+        return properties;
+    }
+
+    /**
+     * Return the local member name
+     */
+    public String getLocalMemberName() {
+        return localMember.toString() ;
+    }
+
+    /**
+     * Return the local member
+     */
+    public Member getLocalMember(boolean alive) {
+        if ( alive && localMember != null && impl != null) localMember.setMemberAliveTime(System.currentTimeMillis()-impl.getServiceStartTime());
+        return localMember;
+    }
+
+    /**
+     * Sets the local member properties for broadcasting
+     */
+    public void setLocalMemberProperties(String listenHost, int listenPort, int securePort, int udpPort) {
+        properties.setProperty("tcpListenHost",listenHost);
+        properties.setProperty("tcpListenPort",String.valueOf(listenPort));
+        properties.setProperty("udpListenPort",String.valueOf(udpPort));
+        properties.setProperty("tcpSecurePort",String.valueOf(securePort));
+        try {
+            if (localMember != null) {
+                localMember.setHostname(listenHost);
+                localMember.setPort(listenPort);
+            } else {
+                localMember = new MemberImpl(listenHost, listenPort, 0);
+                localMember.setUniqueId(UUIDGenerator.randomUUID(true));
+                localMember.setPayload(getPayload());
+                localMember.setDomain(getDomain());
+            }
+            localMember.setSecurePort(securePort);
+            localMember.setUdpPort(udpPort);
+            localMember.getData(true, true);
+        }catch ( IOException x ) {
+            throw new IllegalArgumentException(x);
+        }
+    }
+
+    public void setAddress(String addr) {
+        properties.setProperty("mcastAddress", addr);
+    }
+
+    /**
+     * @deprecated use setAddress
+     * @param addr String
+     */
+    @Deprecated
+    public void setMcastAddr(String addr) {
+        setAddress(addr);
+    }
+
+    public String getAddress() {
+        return properties.getProperty("mcastAddress");
+    }
+
+    /**
+     * @deprecated use getAddress
+     * @return String
+     */
+    @Deprecated
+    public String getMcastAddr() {
+        return getAddress();
+    }
+
+    public void setMcastBindAddress(String bindaddr) {
+        setBind(bindaddr);
+    }
+
+    public void setBind(String bindaddr) {
+        properties.setProperty("mcastBindAddress", bindaddr);
+    }
+    /**
+     * @deprecated use getBind
+     * @return String
+     */
+    @Deprecated
+    public String getMcastBindAddress() {
+        return getBind();
+    }
+
+    public String getBind() {
+        return properties.getProperty("mcastBindAddress");
+    }
+
+    /**
+     * @deprecated use setPort
+     * @param port int
+     */
+    @Deprecated
+    public void setMcastPort(int port) {
+        setPort(port);
+    }
+
+    public void setPort(int port) {
+        properties.setProperty("mcastPort", String.valueOf(port));
+    }
+
+    public void setRecoveryCounter(int recoveryCounter) {
+        properties.setProperty("recoveryCounter", String.valueOf(recoveryCounter));
+    }
+    
+    public int getRecoveryCounter(){
+        String p = properties.getProperty("recoveryCounter");
+        if(p != null){
+            return new Integer(p).intValue();
+        }
+        return -1;
+    }
+
+    public void setRecoveryEnabled(boolean recoveryEnabled) {
+        properties.setProperty("recoveryEnabled", String.valueOf(recoveryEnabled));
+    }
+    
+    public boolean getRecoveryEnabled() {
+        String p = properties.getProperty("recoveryEnabled");
+        if(p != null){
+            return Boolean.valueOf(p).booleanValue();
+        }
+        return false;
+    }
+
+    public void setRecoverySleepTime(long recoverySleepTime) {
+        properties.setProperty("recoverySleepTime", String.valueOf(recoverySleepTime));
+    }
+    
+    public long getRecoverySleepTime(){
+        String p = properties.getProperty("recoverySleepTime");
+        if(p != null){
+            return new Long(p).longValue();
+        }
+        return -1;
+    }
+
+    public void setLocalLoopbackDisabled(boolean localLoopbackDisabled) {
+        properties.setProperty("localLoopbackDisabled",String.valueOf(localLoopbackDisabled));
+    }
+    
+    public boolean getLocalLoopbackDisabled(boolean localLoopbackDisabled) {
+        String p = properties.getProperty("localLoopbackDisabled");
+        if(p != null){
+            return Boolean.valueOf(p).booleanValue();
+        }
+        return false;
+    }
+
+    /**
+     * @deprecated use getPort()
+     * @return int
+     */
+    @Deprecated
+    public int getMcastPort() {
+        return getPort();
+    }
+    public int getPort() {
+        String p = properties.getProperty("mcastPort");
+        return new Integer(p).intValue();
+    }
+
+    /**
+     * @deprecated use setFrequency
+     * @param time long
+     */
+    @Deprecated
+    public void setMcastFrequency(long time) {
+        setFrequency(time);
+    }
+
+    public void setFrequency(long time) {
+        properties.setProperty("mcastFrequency", String.valueOf(time));
+    }
+
+    /**
+     * @deprecated use getFrequency
+     * @return long
+     */
+    @Deprecated
+    public long getMcastFrequency() {
+        return getFrequency();
+    }
+
+    public long getFrequency() {
+        String p = properties.getProperty("mcastFrequency");
+        return new Long(p).longValue();
+    }
+
+    public void setMcastDropTime(long time) {
+        setDropTime(time);
+    }
+    public void setDropTime(long time) {
+        properties.setProperty("memberDropTime", String.valueOf(time));
+    }
+
+    /**
+     * @deprecated use getDropTime
+     * @return long
+     */
+    @Deprecated
+    public long getMcastDropTime() {
+        return getDropTime();
+    }
+
+    public long getDropTime() {
+        String p = properties.getProperty("memberDropTime");
+        return new Long(p).longValue();
+    }
+
+    /**
+     * Check if a required property is available.
+     * @param properties The set of properties
+     * @param name The property to check for
+     */
+    protected void hasProperty(Properties properties, String name){
+        if ( properties.getProperty(name)==null) throw new IllegalArgumentException("McastService:Required property \""+name+"\" is missing.");
+    }
+
+    /**
+     * Start broadcasting and listening to membership pings
+     * @throws java.lang.Exception if a IO error occurs
+     */
+    public void start() throws java.lang.Exception {
+        start(MembershipService.MBR_RX);
+        start(MembershipService.MBR_TX);
+    }
+
+    public void start(int level) throws java.lang.Exception {
+        hasProperty(properties,"mcastPort");
+        hasProperty(properties,"mcastAddress");
+        hasProperty(properties,"memberDropTime");
+        hasProperty(properties,"mcastFrequency");
+        hasProperty(properties,"tcpListenPort");
+        hasProperty(properties,"tcpListenHost");
+        hasProperty(properties,"tcpSecurePort");
+        hasProperty(properties,"udpListenPort");
+
+
+        if ( impl != null ) {
+            impl.start(level);
+            return;
+        }
+        String host = getProperties().getProperty("tcpListenHost");
+        int port = Integer.parseInt(getProperties().getProperty("tcpListenPort"));
+        int securePort = Integer.parseInt(getProperties().getProperty("tcpSecurePort"));
+        int udpPort = Integer.parseInt(getProperties().getProperty("udpListenPort"));
+
+        if ( localMember == null ) {
+            localMember = new MemberImpl(host, port, 100);
+            localMember.setUniqueId(UUIDGenerator.randomUUID(true));
+        } else {
+            localMember.setHostname(host);
+            localMember.setPort(port);
+            localMember.setMemberAliveTime(100);
+        }
+        localMember.setSecurePort(securePort);
+        localMember.setUdpPort(udpPort);
+        if ( this.payload != null ) localMember.setPayload(payload);
+        if ( this.domain != null ) localMember.setDomain(domain);
+        localMember.setServiceStartTime(System.currentTimeMillis());
+        java.net.InetAddress bind = null;
+        if ( properties.getProperty("mcastBindAddress")!= null ) {
+            bind = java.net.InetAddress.getByName(properties.getProperty("mcastBindAddress"));
+        }
+        int ttl = -1;
+        int soTimeout = -1;
+        if ( properties.getProperty("mcastTTL") != null ) {
+            try {
+                ttl = Integer.parseInt(properties.getProperty("mcastTTL"));
+            } catch ( Exception x ) {
+                log.error("Unable to parse mcastTTL="+properties.getProperty("mcastTTL"),x);
+            }
+        }
+        if ( properties.getProperty("mcastSoTimeout") != null ) {
+            try {
+                soTimeout = Integer.parseInt(properties.getProperty("mcastSoTimeout"));
+            } catch ( Exception x ) {
+                log.error("Unable to parse mcastSoTimeout="+properties.getProperty("mcastSoTimeout"),x);
+            }
+        }
+
+        impl = new McastServiceImpl(localMember,Long.parseLong(properties.getProperty("mcastFrequency")),
+                                    Long.parseLong(properties.getProperty("memberDropTime")),
+                                    Integer.parseInt(properties.getProperty("mcastPort")),
+                                    bind,
+                                    java.net.InetAddress.getByName(properties.getProperty("mcastAddress")),
+                                    ttl,
+                                    soTimeout,
+                                    this,
+                                    this,
+                                    Boolean.valueOf(properties.getProperty("localLoopbackDisabled","false")).booleanValue());
+        String value = properties.getProperty("recoveryEnabled","true");
+        boolean recEnabled = Boolean.valueOf(value).booleanValue() ;
+        impl.setRecoveryEnabled(recEnabled);
+        int recCnt = Integer.parseInt(properties.getProperty("recoveryCounter","10"));
+        impl.setRecoveryCounter(recCnt);
+        long recSlpTime = Long.parseLong(properties.getProperty("recoverySleepTime","5000"));
+        impl.setRecoverySleepTime(recSlpTime);
+
+
+        impl.start(level);
+
+
+    }
+
+
+    /**
+     * Stop broadcasting and listening to membership pings
+     */
+    public void stop(int svc) {
+        try  {
+            if ( impl != null && impl.stop(svc) ) impl = null;
+        } catch ( Exception x)  {
+            log.error("Unable to stop the mcast service, level:"+svc+".",x);
+        }
+    }
+
+
+    /**
+     * Return all the members by name
+     */
+    public String[] getMembersByName() {
+        Member[] currentMembers = getMembers();
+        String [] membernames ;
+        if(currentMembers != null) {
+            membernames = new String[currentMembers.length];
+            for (int i = 0; i < currentMembers.length; i++) {
+                membernames[i] = currentMembers[i].toString() ;
+            }
+        } else
+            membernames = new String[0] ;
+        return membernames ;
+    }
+
+    /**
+     * Return the member by name
+     */
+    public Member findMemberByName(String name) {
+        Member[] currentMembers = getMembers();
+        for (int i = 0; i < currentMembers.length; i++) {
+            if (name.equals(currentMembers[i].toString()))
+                return currentMembers[i];
+        }
+        return null;
+    }
+
+    /**
+     * has members?
+     */
+    public boolean hasMembers() {
+       if ( impl == null || impl.membership == null ) return false;
+       return impl.membership.hasMembers();
+    }
+
+    public Member getMember(Member mbr) {
+        if ( impl == null || impl.membership == null ) return null;
+        return impl.membership.getMember(mbr);
+    }
+
+    /**
+     * Return all the members
+     */
+    protected static final Member[]EMPTY_MEMBERS = new Member[0];
+    public Member[] getMembers() {
+        if ( impl == null || impl.membership == null ) return EMPTY_MEMBERS;
+        return impl.membership.getMembers();
+    }
+    /**
+     * Add a membership listener, this version only supports one listener per service,
+     * so calling this method twice will result in only the second listener being active.
+     * @param listener The listener
+     */
+    public void setMembershipListener(MembershipListener listener) {
+        this.listener = listener;
+    }
+    
+    public void setMessageListener(MessageListener listener) {
+        this.msglistener = listener;
+    }
+    
+    public void removeMessageListener() {
+        this.msglistener = null;
+    }
+    /**
+     * Remove the membership listener
+     */
+    public void removeMembershipListener(){
+        listener = null;
+    }
+
+    public void memberAdded(Member member) {
+        if ( listener!=null ) listener.memberAdded(member);
+    }
+
+    /**
+     * Callback from the impl when a new member has been received
+     * @param member The member
+     */
+    public void memberDisappeared(Member member)
+    {
+        if ( listener!=null ) listener.memberDisappeared(member);
+    }
+    
+    public void messageReceived(ChannelMessage msg) {
+        if (msglistener!=null && msglistener.accept(msg)) msglistener.messageReceived(msg); 
+    }
+    
+    public boolean accept(ChannelMessage msg) {
+        return true;
+    }
+    public void broadcast(ChannelMessage message) throws ChannelException {
+        if (impl==null || (impl.startLevel & Channel.MBR_TX_SEQ)!=Channel.MBR_TX_SEQ )
+            throw new ChannelException("Multicast send is not started or enabled.");
+        
+        byte[] data = XByteBuffer.createDataPackage((ChannelData)message);
+        if (data.length>McastServiceImpl.MAX_PACKET_SIZE) {
+            throw new ChannelException("Packet length["+data.length+"] exceeds max packet size of "+McastServiceImpl.MAX_PACKET_SIZE+" bytes.");
+        }
+        DatagramPacket packet = new DatagramPacket(data,0,data.length);
+        try {
+            impl.send(false, packet);
+        } catch (Exception x) {
+            throw new ChannelException(x);
+        }
+    }
+
+    /**
+     * @deprecated use getSoTimeout
+     * @return int
+     */
+    @Deprecated
+    public int getMcastSoTimeout() {
+        return getSoTimeout();
+    }
+
+    public int getSoTimeout() {
+        return mcastSoTimeout;
+    }
+
+    /**
+     * @deprecated use setSoTimeout
+     * @param mcastSoTimeout int
+     */
+    @Deprecated
+    public void setMcastSoTimeout(int mcastSoTimeout) {
+        setSoTimeout(mcastSoTimeout);
+    }
+
+    public void setSoTimeout(int mcastSoTimeout) {
+        this.mcastSoTimeout = mcastSoTimeout;
+        properties.setProperty("mcastSoTimeout", String.valueOf(mcastSoTimeout));
+    }
+
+    /**
+     * @deprecated use getTtl
+     * @return int
+     */
+    @Deprecated
+    public int getMcastTTL() {
+        return getTtl();
+    }
+
+    public int getTtl() {
+        return mcastTTL;
+    }
+
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    public byte[] getDomain() {
+        return domain;
+    }
+
+    /**
+     * @deprecated use setTtl
+     * @param mcastTTL int
+     */
+    @Deprecated
+    public void setMcastTTL(int mcastTTL) {
+        setTtl(mcastTTL);
+    }
+
+    public void setTtl(int mcastTTL) {
+        this.mcastTTL = mcastTTL;
+        properties.setProperty("mcastTTL", String.valueOf(mcastTTL));
+    }
+
+    public void setPayload(byte[] payload) {
+        this.payload = payload;
+        if ( localMember != null ) {
+            localMember.setPayload(payload);
+            localMember.getData(true,true);
+            try {
+                if (impl != null) impl.send(false);
+            }catch ( Exception x ) {
+                log.error("Unable to send payload update.",x);
+            }
+        }
+    }
+
+    public void setDomain(byte[] domain) {
+        this.domain = domain;
+        if ( localMember != null ) {
+            localMember.setDomain(domain);
+            localMember.getData(true,true);
+            try {
+                if (impl != null) impl.send(false);
+            }catch ( Exception x ) {
+                log.error("Unable to send domain update.",x);
+            }
+        }
+    }
+
+    public void setDomain(String domain) {
+        if ( domain == null ) return;
+        if ( domain.startsWith("{") ) setDomain(Arrays.fromString(domain));
+        else setDomain(Arrays.convert(domain));
+    }
+
+    /**
+     * Simple test program
+     * @param args Command-line arguments
+     * @throws Exception If an error occurs
+     */
+    public static void main(String args[]) throws Exception {
+        if(log.isInfoEnabled())
+            log.info("Usage McastService hostname tcpport");
+        McastService service = new McastService();
+        java.util.Properties p = new java.util.Properties();
+        p.setProperty("mcastPort","5555");
+        p.setProperty("mcastAddress","224.10.10.10");
+        p.setProperty("mcastClusterDomain","catalina");
+        p.setProperty("bindAddress","localhost");
+        p.setProperty("memberDropTime","3000");
+        p.setProperty("mcastFrequency","500");
+        p.setProperty("tcpListenPort","4000");
+        p.setProperty("tcpListenHost","127.0.0.1");
+        service.setProperties(p);
+        service.start();
+        Thread.sleep(60*1000*60);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/McastServiceImpl.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/McastServiceImpl.java
new file mode 100644
index 0000000..3df39d5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/McastServiceImpl.java
@@ -0,0 +1,667 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+
+import java.io.IOException;
+import java.net.BindException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.net.SocketTimeoutException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.MessageListener;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.util.ExecutorFactory;
+
+/**
+ * A <b>membership</b> implementation using simple multicast.
+ * This is the representation of a multicast membership service.
+ * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+ * If a node fails to send out a heartbeat, the node will be dismissed.
+ * This is the low level implementation that handles the multicasting sockets.
+ * Need to fix this, could use java.nio and only need one thread to send and receive, or
+ * just use a timeout on the receive
+ * @author Filip Hanik
+ * @version $Id: McastServiceImpl.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public class McastServiceImpl
+{
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( McastService.class );
+    
+    protected static int MAX_PACKET_SIZE = 65535;
+    /**
+     * Internal flag used for the listen thread that listens to the multicasting socket.
+     */
+    protected volatile boolean doRunSender = false;
+    protected volatile boolean doRunReceiver = false;
+    protected int startLevel = 0;
+    /**
+     * Socket that we intend to listen to
+     */
+    protected MulticastSocket socket;
+    /**
+     * The local member that we intend to broad cast over and over again
+     */
+    protected MemberImpl member;
+    /**
+     * The multicast address
+     */
+    protected InetAddress address;
+    /**
+     * The multicast port
+     */
+    protected int port;
+    /**
+     * The time it takes for a member to expire.
+     */
+    protected long timeToExpiration;
+    /**
+     * How often to we send out a broadcast saying we are alive, must be smaller than timeToExpiration
+     */
+    protected long sendFrequency;
+    /**
+     * Reuse the sendPacket, no need to create a new one everytime
+     */
+    protected DatagramPacket sendPacket;
+    /**
+     * Reuse the receivePacket, no need to create a new one everytime
+     */
+    protected DatagramPacket receivePacket;
+    /**
+     * The membership, used so that we calculate memberships when they arrive or don't arrive
+     */
+    protected Membership membership;
+    /**
+     * The actual listener, for callback when stuff goes down
+     */
+    protected MembershipListener service;
+    /**
+     * The actual listener for broadcast callbacks
+     */
+    protected MessageListener msgservice;
+    /**
+     * Thread to listen for pings
+     */
+    protected ReceiverThread receiver;
+    /**
+     * Thread to send pings
+     */
+    protected SenderThread sender;
+
+    /**
+     * Time to live for the multicast packets that are being sent out
+     */
+    protected int mcastTTL = -1;
+    /**
+     * Read timeout on the mcast socket
+     */
+    protected int mcastSoTimeout = -1;
+    /**
+     * bind address
+     */
+    protected InetAddress mcastBindAddress = null;
+    
+    /**
+     * nr of times the system has to fail before a recovery is initiated
+     */
+    protected int recoveryCounter = 10;
+    
+    /**
+     * The time the recovery thread sleeps between recovery attempts
+     */
+    protected long recoverySleepTime = 5000;
+    
+    /**
+     * Add the ability to turn on/off recovery
+     */
+    protected boolean recoveryEnabled = true;
+    
+    /**
+     * Dont interrupt the sender/receiver thread, but pass off to an executor
+     */
+    protected ExecutorService executor = ExecutorFactory.newThreadPool(0, 2, 2, TimeUnit.SECONDS);
+    
+    /**
+     * disable/enable local loopback message
+     */
+    protected boolean localLoopbackDisabled = false;
+    
+    /**
+     * Create a new mcast service impl
+     * @param member - the local member
+     * @param sendFrequency - the time (ms) in between pings sent out
+     * @param expireTime - the time (ms) for a member to expire
+     * @param port - the mcast port
+     * @param bind - the bind address (not sure this is used yet)
+     * @param mcastAddress - the mcast address
+     * @param service - the callback service
+     * @param localLoopbackDisabled - disable loopbackMode
+     * @throws IOException
+     */
+    public McastServiceImpl(
+        MemberImpl member,
+        long sendFrequency,
+        long expireTime,
+        int port,
+        InetAddress bind,
+        InetAddress mcastAddress,
+        int ttl,
+        int soTimeout,
+        MembershipListener service,
+        MessageListener msgservice,
+        boolean localLoopbackDisabled)
+    throws IOException {
+        this.member = member;
+        this.address = mcastAddress;
+        this.port = port;
+        this.mcastSoTimeout = soTimeout;
+        this.mcastTTL = ttl;
+        this.mcastBindAddress = bind;
+        this.timeToExpiration = expireTime;
+        this.service = service;
+        this.msgservice = msgservice;
+        this.sendFrequency = sendFrequency;
+        this.localLoopbackDisabled = localLoopbackDisabled;
+        init();
+    }
+
+    public void init() throws IOException {
+        setupSocket();
+        sendPacket = new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
+        sendPacket.setAddress(address);
+        sendPacket.setPort(port);
+        receivePacket = new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
+        receivePacket.setAddress(address);
+        receivePacket.setPort(port);
+        member.setCommand(new byte[0]);
+        member.getData(true, true);
+        if ( membership == null ) membership = new Membership(member);
+    }
+    
+    protected void setupSocket() throws IOException {
+        if (mcastBindAddress != null) {
+            try {
+                log.info("Attempting to bind the multicast socket to "+address+":"+port);
+                socket = new MulticastSocket(new InetSocketAddress(address,port));
+            } catch (BindException e) {
+                /*
+                 * On some platforms (e.g. Linux) it is not possible to bind
+                 * to the multicast address. In this case only bind to the
+                 * port.
+                 */
+                log.info("Binding to multicast address, failed. Binding to port only.");
+                socket = new MulticastSocket(port);
+            }
+        } else {
+            socket = new MulticastSocket(port);
+        }
+        socket.setLoopbackMode(localLoopbackDisabled); //hint if we want disable loop back(local machine) messages
+        if (mcastBindAddress != null) {
+            if(log.isInfoEnabled())
+                log.info("Setting multihome multicast interface to:" +mcastBindAddress);
+            socket.setInterface(mcastBindAddress);
+        } //end if
+        //force a so timeout so that we don't block forever
+        if ( mcastSoTimeout <= 0 ) mcastSoTimeout = (int)sendFrequency;
+        if(log.isInfoEnabled())
+            log.info("Setting cluster mcast soTimeout to "+mcastSoTimeout);
+        socket.setSoTimeout(mcastSoTimeout);
+
+        if ( mcastTTL >= 0 ) {
+            if(log.isInfoEnabled())
+                log.info("Setting cluster mcast TTL to " + mcastTTL);
+            socket.setTimeToLive(mcastTTL);
+        }
+    }
+
+
+    /**
+     * Start the service
+     * @param level 1 starts the receiver, level 2 starts the sender
+     * @throws IOException if the service fails to start
+     * @throws IllegalStateException if the service is already started
+     */
+    public synchronized void start(int level) throws IOException {
+        boolean valid = false;
+        if ( (level & Channel.MBR_RX_SEQ)==Channel.MBR_RX_SEQ ) {
+            if ( receiver != null ) throw new IllegalStateException("McastService.receive already running.");
+            try {
+                if ( sender == null ) socket.joinGroup(address);
+            }catch (IOException iox) {
+                log.error("Unable to join multicast group, make sure your system has multicasting enabled.");
+                throw iox;
+            }
+            doRunReceiver = true;
+            receiver = new ReceiverThread();
+            receiver.setDaemon(true);
+            receiver.start();
+            valid = true;
+        } 
+        if ( (level & Channel.MBR_TX_SEQ)==Channel.MBR_TX_SEQ ) {
+            if ( sender != null ) throw new IllegalStateException("McastService.send already running.");
+            if ( receiver == null ) socket.joinGroup(address);
+            //make sure at least one packet gets out there
+            send(false);
+            doRunSender = true;
+            sender = new SenderThread(sendFrequency);
+            sender.setDaemon(true);
+            sender.start();
+            //we have started the receiver, but not yet waited for membership to establish
+            valid = true;
+        } 
+        if (!valid) {
+            throw new IllegalArgumentException("Invalid start level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ");
+        }
+        //pause, once or twice
+        waitForMembers(level);
+        startLevel = (startLevel | level);
+    }
+
+    private void waitForMembers(int level) {
+        long memberwait = sendFrequency*2;
+        if(log.isInfoEnabled())
+            log.info("Sleeping for "+memberwait+" milliseconds to establish cluster membership, start level:"+level);
+        try {Thread.sleep(memberwait);}catch (InterruptedException ignore){}
+        if(log.isInfoEnabled())
+            log.info("Done sleeping, membership established, start level:"+level);
+    }
+
+    /**
+     * Stops the service
+     * @throws IOException if the service fails to disconnect from the sockets
+     */
+    public synchronized boolean stop(int level) throws IOException {
+        boolean valid = false;
+        
+        if ( (level & Channel.MBR_RX_SEQ)==Channel.MBR_RX_SEQ ) {
+            valid = true;
+            doRunReceiver = false;
+            if ( receiver !=null ) receiver.interrupt();
+            receiver = null;
+        } 
+        if ( (level & Channel.MBR_TX_SEQ)==Channel.MBR_TX_SEQ ) {
+            valid = true;
+            doRunSender = false;
+            if ( sender != null )sender.interrupt();
+            sender = null;
+        } 
+        
+        if (!valid) {
+            throw new IllegalArgumentException("Invalid stop level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ");
+        }
+        startLevel = (startLevel & (~level));
+        //we're shutting down, send a shutdown message and close the socket
+        if ( startLevel == 0 ) {
+            //send a stop message
+            member.setCommand(Member.SHUTDOWN_PAYLOAD);
+            member.getData(true, true);
+            send(false);
+            //leave mcast group
+            try {socket.leaveGroup(address);}catch ( Exception ignore){}
+            try {socket.close();}catch ( Exception ignore){}
+            member.setServiceStartTime(-1);
+        }
+        return (startLevel == 0);
+    }
+
+    /**
+     * Receive a datagram packet, locking wait
+     * @throws IOException
+     */
+    public void receive() throws IOException {
+        boolean checkexpired = true;
+        try {
+            
+            socket.receive(receivePacket);
+            if(receivePacket.getLength() > MAX_PACKET_SIZE) {
+                log.error("Multicast packet received was too long, dropping package:"+receivePacket.getLength());
+            } else {
+                byte[] data = new byte[receivePacket.getLength()];
+                System.arraycopy(receivePacket.getData(), receivePacket.getOffset(), data, 0, data.length);
+                if (XByteBuffer.firstIndexOf(data,0,MemberImpl.TRIBES_MBR_BEGIN)==0) {
+                    memberDataReceived(data);
+                } else {
+                    memberBroadcastsReceived(data);
+                }
+                
+            }
+        } catch (SocketTimeoutException x ) { 
+            //do nothing, this is normal, we don't want to block forever
+            //since the receive thread is the same thread
+            //that does membership expiration
+        }
+        if (checkexpired) checkExpired();
+    }
+
+    private void memberDataReceived(byte[] data) {
+        final MemberImpl m = MemberImpl.getMember(data);
+        if (log.isTraceEnabled()) log.trace("Mcast receive ping from member " + m);
+        Runnable t = null;
+        if (Arrays.equals(m.getCommand(), Member.SHUTDOWN_PAYLOAD)) {
+            if (log.isDebugEnabled()) log.debug("Member has shutdown:" + m);
+            membership.removeMember(m);
+            t = new Runnable() {
+                @Override
+                public void run() {
+                    String name = Thread.currentThread().getName();
+                    try {
+                        Thread.currentThread().setName("Membership-MemberDisappeared.");
+                        service.memberDisappeared(m);
+                    }finally {
+                        Thread.currentThread().setName(name);
+                    }
+                }
+            };
+        } else if (membership.memberAlive(m)) {
+            if (log.isDebugEnabled()) log.debug("Mcast add member " + m);
+            t = new Runnable() {
+                @Override
+                public void run() {
+                    String name = Thread.currentThread().getName();
+                    try {
+                        Thread.currentThread().setName("Membership-MemberAdded.");
+                        service.memberAdded(m);
+                    }finally {
+                        Thread.currentThread().setName(name);
+                    }
+                }
+            };
+        } //end if
+        if ( t != null ) {
+            executor.execute(t);
+        }
+    }
+    
+    private void memberBroadcastsReceived(final byte[] b) {
+        if (log.isTraceEnabled()) log.trace("Mcast received broadcasts.");
+        XByteBuffer buffer = new XByteBuffer(b,true);
+        if (buffer.countPackages(true)>0) {
+            int count = buffer.countPackages();
+            final ChannelData[] data = new ChannelData[count];
+            for (int i=0; i<count; i++) {
+                try {
+                    data[i] = buffer.extractPackage(true);
+                }catch (IllegalStateException ise) {
+                    log.debug("Unable to decode message.",ise);
+                }catch (IOException x) {
+                    log.debug("Unable to decode message.",x);
+                }
+            }
+            Runnable t = new Runnable() {
+                @Override
+                public void run() {
+                    String name = Thread.currentThread().getName();
+                    try {
+                        Thread.currentThread().setName("Membership-MemberAdded.");
+                        for (int i=0; i<data.length; i++ ) {
+                            try {
+                                if (data[i]!=null && !member.equals(data[i].getAddress())) {
+                                    msgservice.messageReceived(data[i]);
+                                }
+                            } catch (Throwable t) {
+                                if (t instanceof ThreadDeath) {
+                                    throw (ThreadDeath) t;
+                                }
+                                if (t instanceof VirtualMachineError) {
+                                    throw (VirtualMachineError) t;
+                                }
+                                log.error("Unable to receive broadcast message.",t);
+                            }
+                        }
+                    }finally {
+                        Thread.currentThread().setName(name);
+                    }
+                }
+            };
+            executor.execute(t);
+        }
+    }
+
+    protected final Object expiredMutex = new Object();
+    protected void checkExpired() {
+        synchronized (expiredMutex) {
+            MemberImpl[] expired = membership.expire(timeToExpiration);
+            for (int i = 0; i < expired.length; i++) {
+                final MemberImpl member = expired[i];
+                if (log.isDebugEnabled())
+                    log.debug("Mcast exipre  member " + expired[i]);
+                try {
+                    Runnable t = new Runnable() {
+                        @Override
+                        public void run() {
+                            String name = Thread.currentThread().getName();
+                            try {
+                                Thread.currentThread().setName("Membership-MemberExpired.");
+                                service.memberDisappeared(member);
+                            }finally {
+                                Thread.currentThread().setName(name);
+                            }
+                            
+                        }
+                    };
+                    executor.execute(t);
+                } catch (Exception x) {
+                    log.error("Unable to process member disappeared message.", x);
+                }
+            }
+        }
+    }
+
+    /**
+     * Send a ping
+     * @throws IOException
+     */ 
+    public void send(boolean checkexpired) throws IOException{
+        send(checkexpired,null);
+    }
+    
+    private final Object sendLock = new Object();
+
+    public void send(boolean checkexpired, DatagramPacket packet) throws IOException{
+        checkexpired = (checkexpired && (packet==null));
+        //ignore if we haven't started the sender
+        //if ( (startLevel&Channel.MBR_TX_SEQ) != Channel.MBR_TX_SEQ ) return;
+        if (packet==null) {
+            member.inc();
+            if(log.isTraceEnabled()) {
+                log.trace("Mcast send ping from member " + member);
+            }
+            byte[] data = member.getData();
+            packet = new DatagramPacket(data,data.length);
+        } else if (log.isTraceEnabled()) {
+            log.trace("Sending message broadcast "+packet.getLength()+ " bytes from "+ member);
+        }
+        packet.setAddress(address);
+        packet.setPort(port);
+        //TODO this operation is not thread safe
+        synchronized (sendLock) {
+            socket.send(packet);
+        }
+        if ( checkexpired ) checkExpired();
+    }
+
+    public long getServiceStartTime() {
+        return (member!=null) ? member.getServiceStartTime() : -1l;
+    }
+
+    public int getRecoveryCounter() {
+        return recoveryCounter;
+    }
+
+    public boolean isRecoveryEnabled() {
+        return recoveryEnabled;
+    }
+
+    public long getRecoverySleepTime() {
+        return recoverySleepTime;
+    }
+
+    public class ReceiverThread extends Thread {
+        int errorCounter = 0;
+        public ReceiverThread() {
+            super();
+            setName("Tribes-MembershipReceiver");
+        }
+        @Override
+        public void run() {
+            while ( doRunReceiver ) {
+                try {
+                    receive();
+                    errorCounter=0;
+                } catch ( ArrayIndexOutOfBoundsException ax ) {
+                    //we can ignore this, as it means we have an invalid package
+                    //but we will log it to debug
+                    if ( log.isDebugEnabled() )
+                        log.debug("Invalid member mcast package.",ax);
+                } catch ( Exception x ) {
+                    if (x instanceof InterruptedException) interrupted();
+                    else {
+                        if (errorCounter==0 && doRunReceiver) log.warn("Error receiving mcast package. Sleeping 500ms",x);
+                        else if (log.isDebugEnabled()) log.debug("Error receiving mcast package"+(doRunReceiver?". Sleeping 500ms":"."),x);
+                        if (doRunReceiver) {
+                            try { Thread.sleep(500); } catch ( Exception ignore ){}
+                            if ( (++errorCounter)>=recoveryCounter ) {
+                                errorCounter=0;
+                                RecoveryThread.recover(McastServiceImpl.this);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }//class ReceiverThread
+
+    public class SenderThread extends Thread {
+        long time;
+        int errorCounter=0;
+        public SenderThread(long time) {
+            this.time = time;
+            setName("Tribes-MembershipSender");
+
+        }
+        @Override
+        public void run() {
+            while ( doRunSender ) {
+                try {
+                    send(true);
+                    errorCounter = 0;
+                } catch ( Exception x ) {
+                    if (errorCounter==0) log.warn("Unable to send mcast message.",x);
+                    else log.debug("Unable to send mcast message.",x);
+                    if ( (++errorCounter)>=recoveryCounter ) {
+                        errorCounter=0;
+                        RecoveryThread.recover(McastServiceImpl.this);
+                    }
+                }
+                try { Thread.sleep(time); } catch ( Exception ignore ) {}
+            }
+        }
+    }//class SenderThread
+
+    protected static class RecoveryThread extends Thread {
+        static volatile boolean running = false;
+
+        public static synchronized void recover(McastServiceImpl parent) {
+            if (running) return;
+            if (!parent.isRecoveryEnabled())
+                return;
+            
+            running = true;
+            
+            Thread t = new RecoveryThread(parent);
+            
+            t.setName("Tribes-MembershipRecovery");
+            t.setDaemon(true);
+            t.start();
+        }
+
+
+        McastServiceImpl parent = null;
+        public RecoveryThread(McastServiceImpl parent) {
+            this.parent = parent;
+        }
+        
+        public boolean stopService() {
+            try {
+                parent.stop(Channel.MBR_RX_SEQ | Channel.MBR_TX_SEQ);
+                return true;
+            } catch (Exception x) {
+                log.warn("Recovery thread failed to stop membership service.", x);
+                return false;
+            }
+        }
+        public boolean startService() {
+            try {
+                parent.init();
+                parent.start(Channel.MBR_RX_SEQ | Channel.MBR_TX_SEQ);
+                return true;
+            } catch (Exception x) {
+                log.warn("Recovery thread failed to start membership service.", x);
+                return false;
+            }
+        }
+        @Override
+        public void run() {
+            boolean success = false;
+            int attempt = 0;
+            try {
+                while (!success) {
+                    if(log.isInfoEnabled())
+                        log.info("Tribes membership, running recovery thread, multicasting is not functional.");
+                    if (stopService() & startService()) {
+                        success = true;
+                        if(log.isInfoEnabled())
+                            log.info("Membership recovery was successful.");
+                    }
+                    try {
+                        if (!success) {
+                            if(log.isInfoEnabled())
+                                log.info("Recovery attempt "+(++attempt)+" failed, trying again in " +parent.recoverySleepTime+ " seconds");
+                            Thread.sleep(parent.recoverySleepTime);
+                        }
+                    }catch (InterruptedException ignore) {
+                    }
+                }
+            }finally {
+                running = false;
+            }
+        }
+    }
+
+    public void setRecoveryCounter(int recoveryCounter) {
+        this.recoveryCounter = recoveryCounter;
+    }
+
+    public void setRecoveryEnabled(boolean recoveryEnabled) {
+        this.recoveryEnabled = recoveryEnabled;
+    }
+
+    public void setRecoverySleepTime(long recoverySleepTime) {
+        this.recoverySleepTime = recoverySleepTime;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/MemberImpl.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/MemberImpl.java
new file mode 100644
index 0000000..7666fad
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/MemberImpl.java
@@ -0,0 +1,636 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Arrays;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.SenderState;
+
+/**
+ * A <b>membership</b> implementation using simple multicast.
+ * This is the representation of a multicast member.
+ * Carries the host, and port of the this or other cluster nodes.
+ *
+ * @author Filip Hanik
+ * @version $Id: MemberImpl.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public class MemberImpl implements Member, java.io.Externalizable {
+
+    /**
+     * Should a call to getName or getHostName try to do a DNS lookup?
+     * default is false
+     */
+    public static final boolean DO_DNS_LOOKUPS = Boolean.parseBoolean(System.getProperty("org.apache.catalina.tribes.dns_lookups","false"));
+    /**
+     * Public properties specific to this implementation
+     */
+    public static final transient String TCP_LISTEN_PORT = "tcpListenPort";
+    public static final transient String TCP_LISTEN_HOST = "tcpListenHost";
+    public static final transient String MEMBER_NAME = "memberName";
+
+    public static final transient byte[] TRIBES_MBR_BEGIN = new byte[] {84, 82, 73, 66, 69, 83, 45, 66, 1, 0};
+    public static final transient byte[] TRIBES_MBR_END   = new byte[] {84, 82, 73, 66, 69, 83, 45, 69, 1, 0};
+
+    /**
+     * The listen host for this member
+     */
+    protected byte[] host;
+    protected transient String hostname;
+    /**
+     * The tcp listen port for this member
+     */
+    protected int port;
+    /**
+     * The udp listen port for this member
+     */
+    protected int udpPort = -1;
+
+    /**
+     * The tcp/SSL listen port for this member
+     */
+    protected int securePort = -1;
+
+    /**
+     * Counter for how many broadcast messages have been sent from this member
+     */
+    protected int msgCount = 0;
+    /**
+     * The number of milliseconds since this member was
+     * created, is kept track of using the start time
+     */
+    protected long memberAliveTime = 0;
+
+    /**
+     * For the local member only
+     */
+    protected transient long serviceStartTime;
+
+    /**
+     * To avoid serialization over and over again, once the local dataPkg
+     * has been set, we use that to transmit data
+     */
+    protected transient byte[] dataPkg = null;
+
+    /**
+     * Unique session Id for this member
+     */
+    protected byte[] uniqueId = new byte[16];
+
+    /**
+     * Custom payload that an app framework can broadcast
+     * Also used to transport stop command.
+     */
+    protected byte[] payload = new byte[0];
+
+    /**
+     * Command, so that the custom payload doesn't have to be used
+     * This is for internal tribes use, such as SHUTDOWN_COMMAND
+     */
+    protected byte[] command = new byte[0];
+
+    /**
+     * Domain if we want to filter based on domain.
+     */
+    protected byte[] domain = new byte[0];
+
+    /**
+     * Empty constructor for serialization
+     */
+    public MemberImpl() {
+
+    }
+
+    /**
+     * Construct a new member object
+     * @param host - the tcp listen host
+     * @param port - the tcp listen port
+     * @param aliveTime - the number of milliseconds since this member was created
+     */
+    public MemberImpl(String host,
+                      int port,
+                      long aliveTime) throws IOException {
+        setHostname(host);
+        this.port = port;
+        this.memberAliveTime=aliveTime;
+    }
+
+    public MemberImpl(String host,
+                      int port,
+                      long aliveTime,
+                      byte[] payload) throws IOException {
+        this(host,port,aliveTime);
+        setPayload(payload);
+    }
+
+    public boolean isReady() {
+        return SenderState.getSenderState(this).isReady();
+    }
+    public boolean isSuspect() {
+        return SenderState.getSenderState(this).isSuspect();
+    }
+    public boolean isFailing() {
+        return SenderState.getSenderState(this).isFailing();
+    }
+
+    /**
+     * Increment the message count.
+     */
+    protected void inc() {
+        msgCount++;
+    }
+
+    /**
+     * Create a data package to send over the wire representing this member.
+     * This is faster than serialization.
+     * @return - the bytes for this member deserialized
+     */
+    public byte[] getData()  {
+        return getData(true);
+    }
+    /**
+     * Highly optimized version of serializing a member into a byte array
+     * Returns a cached byte[] reference, do not modify this data
+     * @param getalive boolean
+     * @return byte[]
+     */
+    public byte[] getData(boolean getalive)  {
+        return getData(getalive,false);
+    }
+
+
+    public int getDataLength() {
+        return TRIBES_MBR_BEGIN.length+ //start pkg
+               4+ //data length
+               8+ //alive time
+               4+ //port
+               4+ //secure port
+               4+ //udp port
+               1+ //host length
+               host.length+ //host
+               4+ //command length
+               command.length+ //command
+               4+ //domain length
+               domain.length+ //domain
+               16+ //unique id
+               4+ //payload length
+               payload.length+ //payload
+               TRIBES_MBR_END.length; //end pkg
+    }
+
+    /**
+     *
+     * @param getalive boolean - calculate memberAlive time
+     * @param reset boolean - reset the cached data package, and create a new one
+     * @return byte[]
+     */
+    public byte[] getData(boolean getalive, boolean reset)  {
+        if ( reset ) dataPkg = null;
+        //look in cache first
+        if ( dataPkg!=null ) {
+            if ( getalive ) {
+                //you'd be surprised, but System.currentTimeMillis
+                //shows up on the profiler
+                long alive=System.currentTimeMillis()-getServiceStartTime();
+                XByteBuffer.toBytes(alive, dataPkg, TRIBES_MBR_BEGIN.length+4);
+            }
+            return dataPkg;
+        }
+
+        //package looks like
+        //start package TRIBES_MBR_BEGIN.length
+        //package length - 4 bytes
+        //alive - 8 bytes
+        //port - 4 bytes
+        //secure port - 4 bytes
+        //udp port - 4 bytes
+        //host length - 1 byte
+        //host - hl bytes
+        //clen - 4 bytes
+        //command - clen bytes
+        //dlen - 4 bytes
+        //domain - dlen bytes
+        //uniqueId - 16 bytes
+        //payload length - 4 bytes
+        //payload plen bytes
+        //end package TRIBES_MBR_END.length
+        byte[] addr = host;
+        long alive=System.currentTimeMillis()-getServiceStartTime();
+        byte hl = (byte)addr.length;
+        byte[] data = new byte[getDataLength()];
+
+        int bodylength = (getDataLength() - TRIBES_MBR_BEGIN.length - TRIBES_MBR_END.length - 4);
+
+        int pos = 0;
+
+        //TRIBES_MBR_BEGIN
+        System.arraycopy(TRIBES_MBR_BEGIN,0,data,pos,TRIBES_MBR_BEGIN.length);
+        pos += TRIBES_MBR_BEGIN.length;
+
+        //body length
+        XByteBuffer.toBytes(bodylength,data,pos);
+        pos += 4;
+
+        //alive data
+        XByteBuffer.toBytes(alive,data,pos);
+        pos += 8;
+        //port
+        XByteBuffer.toBytes(port,data,pos);
+        pos += 4;
+        //secure port
+        XByteBuffer.toBytes(securePort,data,pos);
+        pos += 4;
+        //udp port
+        XByteBuffer.toBytes(udpPort,data,pos);
+        pos += 4;
+        //host length
+        data[pos++] = hl;
+        //host
+        System.arraycopy(addr,0,data,pos,addr.length);
+        pos+=addr.length;
+        //clen - 4 bytes
+        XByteBuffer.toBytes(command.length,data,pos);
+        pos+=4;
+        //command - clen bytes
+        System.arraycopy(command,0,data,pos,command.length);
+        pos+=command.length;
+        //dlen - 4 bytes
+        XByteBuffer.toBytes(domain.length,data,pos);
+        pos+=4;
+        //domain - dlen bytes
+        System.arraycopy(domain,0,data,pos,domain.length);
+        pos+=domain.length;
+        //unique Id
+        System.arraycopy(uniqueId,0,data,pos,uniqueId.length);
+        pos+=uniqueId.length;
+        //payload
+        XByteBuffer.toBytes(payload.length,data,pos);
+        pos+=4;
+        System.arraycopy(payload,0,data,pos,payload.length);
+        pos+=payload.length;
+
+        //TRIBES_MBR_END
+        System.arraycopy(TRIBES_MBR_END,0,data,pos,TRIBES_MBR_END.length);
+        pos += TRIBES_MBR_END.length;
+
+        //create local data
+        dataPkg = data;
+        return data;
+    }
+    /**
+     * Deserializes a member from data sent over the wire
+     * @param data - the bytes received
+     * @return a member object.
+     */
+    public static MemberImpl getMember(byte[] data, MemberImpl member) {
+        return getMember(data,0,data.length,member);
+    }
+
+    public static MemberImpl getMember(byte[] data, int offset, int length, MemberImpl member) {
+        //package looks like
+        //start package TRIBES_MBR_BEGIN.length
+        //package length - 4 bytes
+        //alive - 8 bytes
+        //port - 4 bytes
+        //secure port - 4 bytes
+        //udp port - 4 bytes
+        //host length - 1 byte
+        //host - hl bytes
+        //clen - 4 bytes
+        //command - clen bytes
+        //dlen - 4 bytes
+        //domain - dlen bytes
+        //uniqueId - 16 bytes
+        //payload length - 4 bytes
+        //payload plen bytes
+        //end package TRIBES_MBR_END.length
+
+        int pos = offset;
+
+        if (XByteBuffer.firstIndexOf(data,offset,TRIBES_MBR_BEGIN)!=pos) {
+            throw new IllegalArgumentException("Invalid package, should start with:"+org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_BEGIN));
+        }
+
+        if ( length < (TRIBES_MBR_BEGIN.length+4) ) {
+            throw new ArrayIndexOutOfBoundsException("Member package to small to validate.");
+        }
+
+        pos += TRIBES_MBR_BEGIN.length;
+
+        int bodylength = XByteBuffer.toInt(data,pos);
+        pos += 4;
+
+        if ( length < (bodylength+4+TRIBES_MBR_BEGIN.length+TRIBES_MBR_END.length) ) {
+            throw new ArrayIndexOutOfBoundsException("Not enough bytes in member package.");
+        }
+
+        int endpos = pos+bodylength;
+        if (XByteBuffer.firstIndexOf(data,endpos,TRIBES_MBR_END)!=endpos) {
+            throw new IllegalArgumentException("Invalid package, should end with:"+org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_END));
+        }
+
+
+        byte[] alived = new byte[8];
+        System.arraycopy(data, pos, alived, 0, 8);
+        pos += 8;
+        byte[] portd = new byte[4];
+        System.arraycopy(data, pos, portd, 0, 4);
+        pos += 4;
+
+        byte[] sportd = new byte[4];
+        System.arraycopy(data, pos, sportd, 0, 4);
+        pos += 4;
+
+        byte[] uportd = new byte[4];
+        System.arraycopy(data, pos, uportd, 0, 4);
+        pos += 4;
+
+
+        byte hl = data[pos++];
+        byte[] addr = new byte[hl];
+        System.arraycopy(data, pos, addr, 0, hl);
+        pos += hl;
+
+        int cl = XByteBuffer.toInt(data, pos);
+        pos += 4;
+
+        byte[] command = new byte[cl];
+        System.arraycopy(data, pos, command, 0, command.length);
+        pos += command.length;
+
+        int dl = XByteBuffer.toInt(data, pos);
+        pos += 4;
+
+        byte[] domain = new byte[dl];
+        System.arraycopy(data, pos, domain, 0, domain.length);
+        pos += domain.length;
+
+        byte[] uniqueId = new byte[16];
+        System.arraycopy(data, pos, uniqueId, 0, 16);
+        pos += 16;
+
+        int pl = XByteBuffer.toInt(data, pos);
+        pos += 4;
+
+        byte[] payload = new byte[pl];
+        System.arraycopy(data, pos, payload, 0, payload.length);
+        pos += payload.length;
+
+        member.setHost(addr);
+        member.setPort(XByteBuffer.toInt(portd, 0));
+        member.setSecurePort(XByteBuffer.toInt(sportd, 0));
+        member.setUdpPort(XByteBuffer.toInt(uportd, 0));
+        member.setMemberAliveTime(XByteBuffer.toLong(alived, 0));
+        member.setUniqueId(uniqueId);
+        member.payload = payload;
+        member.domain = domain;
+        member.command = command;
+
+        member.dataPkg = new byte[length];
+        System.arraycopy(data, offset, member.dataPkg, 0, length);
+
+        return member;
+    }
+
+    public static MemberImpl getMember(byte[] data) {
+       return getMember(data,new MemberImpl());
+    }
+
+    public static MemberImpl getMember(byte[] data, int offset, int length) {
+       return getMember(data,offset,length,new MemberImpl());
+    }
+
+    /**
+     * Return the name of this object
+     * @return a unique name to the cluster
+     */
+    public String getName() {
+        return "tcp://"+getHostname()+":"+getPort();
+    }
+
+    /**
+     * Return the listen port of this member
+     * @return - tcp listen port
+     */
+    public int getPort()  {
+        return this.port;
+    }
+
+    /**
+     * Return the TCP listen host for this member
+     * @return IP address or host name
+     */
+    public byte[] getHost()  {
+        return host;
+    }
+
+    public String getHostname() {
+        if ( this.hostname != null ) return hostname;
+        else {
+            try {
+                if (DO_DNS_LOOKUPS)
+                    this.hostname = java.net.InetAddress.getByAddress(host).getHostName();
+                else
+                    this.hostname = org.apache.catalina.tribes.util.Arrays.toString(host,0,host.length,true);
+                return this.hostname;
+            }catch ( IOException x ) {
+                throw new RuntimeException("Unable to parse hostname.",x);
+            }
+        }
+    }
+
+    /**
+     * Contains information on how long this member has been online.
+     * The result is the number of milli seconds this member has been
+     * broadcasting its membership to the cluster.
+     * @return nr of milliseconds since this member started.
+     */
+    public long getMemberAliveTime() {
+       return memberAliveTime;
+    }
+
+    public long getServiceStartTime() {
+        return serviceStartTime;
+    }
+
+    public byte[] getUniqueId() {
+        return uniqueId;
+    }
+
+    public byte[] getPayload() {
+        return payload;
+    }
+
+    public byte[] getCommand() {
+        return command;
+    }
+
+    public byte[] getDomain() {
+        return domain;
+    }
+
+    public int getSecurePort() {
+        return securePort;
+    }
+
+    public int getUdpPort() {
+        return udpPort;
+    }
+
+    public void setMemberAliveTime(long time) {
+       memberAliveTime=time;
+    }
+
+
+
+    /**
+     * String representation of this object
+     */
+    @Override
+    public String toString()  {
+        StringBuilder buf = new StringBuilder("org.apache.catalina.tribes.membership.MemberImpl[");
+        buf.append(getName()).append(",");
+        buf.append(getHostname()).append(",");
+        buf.append(port).append(", alive=");
+        buf.append(memberAliveTime).append(", ");
+        buf.append("securePort=").append(securePort).append(", ");
+        buf.append("UDP Port=").append(udpPort).append(", ");
+        buf.append("id=").append(bToS(this.uniqueId)).append(", ");
+        buf.append("payload=").append(bToS(this.payload,8)).append(", ");
+        buf.append("command=").append(bToS(this.command,8)).append(", ");
+        buf.append("domain=").append(bToS(this.domain,8)).append(", ");
+        buf.append("]");
+        return buf.toString();
+    }
+    public static String bToS(byte[] data) {
+        return bToS(data,data.length);
+    }
+    public static String bToS(byte[] data, int max) {
+        StringBuilder buf = new StringBuilder(4*16);
+        buf.append("{");
+        for (int i=0; data!=null && i<data.length; i++ ) {
+            buf.append(String.valueOf(data[i])).append(" ");
+            if ( i==max ) {
+                buf.append("...("+data.length+")");
+                break;
+            }
+        }
+        buf.append("}");
+        return buf.toString();
+    }
+
+    /**
+     * @see java.lang.Object#hashCode()
+     * @return The hash code
+     */
+    @Override
+    public int hashCode() {
+        return getHost()[0]+getHost()[1]+getHost()[2]+getHost()[3];
+    }
+
+    /**
+     * Returns true if the param o is a McastMember with the same name
+     * @param o
+     */
+    @Override
+    public boolean equals(Object o) {
+        if ( o instanceof MemberImpl )    {
+            return Arrays.equals(this.getHost(),((MemberImpl)o).getHost()) &&
+                   this.getPort() == ((MemberImpl)o).getPort() &&
+                   Arrays.equals(this.getUniqueId(),((MemberImpl)o).getUniqueId());
+        }
+        else
+            return false;
+    }
+
+    public void setHost(byte[] host) {
+        this.host = host;
+    }
+
+    public void setHostname(String host) throws IOException {
+        hostname = host;
+        this.host = java.net.InetAddress.getByName(host).getAddress();
+    }
+
+    public void setMsgCount(int msgCount) {
+        this.msgCount = msgCount;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+        this.dataPkg = null;
+    }
+
+    public void setServiceStartTime(long serviceStartTime) {
+        this.serviceStartTime = serviceStartTime;
+    }
+
+    public void setUniqueId(byte[] uniqueId) {
+        this.uniqueId = uniqueId!=null?uniqueId:new byte[16];
+        getData(true,true);
+    }
+
+    public void setPayload(byte[] payload) {
+        byte[] oldpayload = this.payload;
+        this.payload = payload!=null?payload:new byte[0];
+        if ( this.getData(true,true).length > McastServiceImpl.MAX_PACKET_SIZE ) {
+            this.payload = oldpayload;
+            throw new IllegalArgumentException("Payload is to large for tribes to handle.");
+        }
+
+    }
+
+    public void setCommand(byte[] command) {
+        this.command = command!=null?command:new byte[0];
+        getData(true,true);
+    }
+
+    public void setDomain(byte[] domain) {
+        this.domain = domain!=null?domain:new byte[0];
+        getData(true,true);
+    }
+
+    public void setSecurePort(int securePort) {
+        this.securePort = securePort;
+        this.dataPkg = null;
+    }
+
+    public void setUdpPort(int port) {
+        this.udpPort = port;
+        this.dataPkg = null;
+    }
+
+    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        int length = in.readInt();
+        byte[] message = new byte[length];
+        in.readFully(message);
+        getMember(message,this);
+
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        byte[] data = this.getData();
+        out.writeInt(data.length);
+        out.write(data);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/Membership.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/Membership.java
new file mode 100644
index 0000000..706e7db
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/Membership.java
@@ -0,0 +1,328 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * A <b>membership</b> implementation using simple multicast.
+ * This is the representation of a multicast membership.
+ * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+ * If a node fails to send out a heartbeat, the node will be dismissed.
+ *
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Id: Membership.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public class Membership implements Cloneable {
+
+    protected static final MemberImpl[] EMPTY_MEMBERS = new MemberImpl[0];
+    
+    private final Object membersLock = new Object();
+    
+    /**
+     * The name of this membership, has to be the same as the name for the local
+     * member
+     */
+    protected MemberImpl local;
+    
+    /**
+     * A map of all the members in the cluster.
+     */
+    protected HashMap<MemberImpl, MbrEntry> map = new HashMap<MemberImpl, MbrEntry>();
+    
+    /**
+     * A list of all the members in the cluster.
+     */
+    protected MemberImpl[] members = EMPTY_MEMBERS;
+    
+    /**
+      * sort members by alive time
+      */
+    protected Comparator<Member> memberComparator = new MemberComparator();
+
+    @Override
+    public Object clone() {
+        synchronized (membersLock) {
+            Membership clone = new Membership(local, memberComparator);
+            @SuppressWarnings("unchecked") // map is correct type already
+            final HashMap<MemberImpl, MbrEntry> tmpclone = (HashMap<MemberImpl, MbrEntry>) map.clone();
+            clone.map = tmpclone;
+            clone.members = new MemberImpl[members.length];
+            System.arraycopy(members,0,clone.members,0,members.length);
+            return clone;
+        }
+    }
+
+    /**
+     * Constructs a new membership
+     * @param local - has to be the name of the local member. Used to filter the local member from the cluster membership
+     * @param includeLocal - TBA
+     */
+    public Membership(MemberImpl local, boolean includeLocal) {
+        this.local = local;
+        if ( includeLocal ) addMember(local);
+    }
+
+    public Membership(MemberImpl local) {
+        this(local,false);
+    }
+
+    public Membership(MemberImpl local, Comparator<Member> comp) {
+        this(local,comp,false);
+    }
+
+    public Membership(MemberImpl local, Comparator<Member> comp, boolean includeLocal) {
+        this(local,includeLocal);
+        this.memberComparator = comp;
+    }
+    /**
+     * Reset the membership and start over fresh.
+     * Ie, delete all the members and wait for them to ping again and join this membership
+     */
+    public synchronized void reset() {
+        map.clear();
+        members = EMPTY_MEMBERS ;
+    }
+
+    /**
+     * Notify the membership that this member has announced itself.
+     *
+     * @param member - the member that just pinged us
+     * @return - true if this member is new to the cluster, false otherwise.<br/>
+     * - false if this member is the local member or updated.
+     */
+    public synchronized boolean memberAlive(MemberImpl member) {
+        boolean result = false;
+        //ignore ourselves
+        if (  member.equals(local) ) return result;
+
+        //return true if the membership has changed
+        MbrEntry entry = map.get(member);
+        if ( entry == null ) {
+            entry = addMember(member);
+            result = true;
+       } else {
+            //update the member alive time
+            MemberImpl updateMember = entry.getMember() ;
+            if(updateMember.getMemberAliveTime() != member.getMemberAliveTime()) {
+                //update fields that can change
+                updateMember.setMemberAliveTime(member.getMemberAliveTime());
+                updateMember.setPayload(member.getPayload());
+                updateMember.setCommand(member.getCommand());
+                Arrays.sort(members, memberComparator);
+            }
+        }
+        entry.accessed();
+        return result;
+    }
+
+    /**
+     * Add a member to this component and sort array with memberComparator
+     * @param member The member to add
+     */
+    public synchronized MbrEntry addMember(MemberImpl member) {
+      synchronized (membersLock) {
+          MbrEntry entry = new MbrEntry(member);
+          if (!map.containsKey(member) ) {
+              map.put(member, entry);
+              MemberImpl results[] = new MemberImpl[members.length + 1];
+              for (int i = 0; i < members.length; i++) results[i] = members[i];
+              results[members.length] = member;
+              members = results;
+              Arrays.sort(members, memberComparator);
+          }
+          return entry;
+      }
+    }
+    
+    /**
+     * Remove a member from this component.
+     * 
+     * @param member The member to remove
+     */
+    public void removeMember(MemberImpl member) {
+        map.remove(member);
+        synchronized (membersLock) {
+            int n = -1;
+            for (int i = 0; i < members.length; i++) {
+                if (members[i] == member || members[i].equals(member)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0) return;
+            MemberImpl results[] = new MemberImpl[members.length - 1];
+            int j = 0;
+            for (int i = 0; i < members.length; i++) {
+                if (i != n)
+                    results[j++] = members[i];
+            }
+            members = results;
+        }
+    }
+    
+    /**
+     * Runs a refresh cycle and returns a list of members that has expired.
+     * This also removes the members from the membership, in such a way that
+     * getMembers() = getMembers() - expire()
+     * @param maxtime - the max time a member can remain unannounced before it is considered dead.
+     * @return the list of expired members
+     */
+    public synchronized MemberImpl[] expire(long maxtime) {
+        if(!hasMembers() )
+           return EMPTY_MEMBERS;
+       
+        ArrayList<MemberImpl> list = null;
+        Iterator<MbrEntry> i = map.values().iterator();
+        while(i.hasNext()) {
+            MbrEntry entry = i.next();
+            if( entry.hasExpired(maxtime) ) {
+                if(list == null) // only need a list when members are expired (smaller gc)
+                    list = new java.util.ArrayList<MemberImpl>();
+                list.add(entry.getMember());
+            }
+        }
+        
+        if(list != null) {
+            MemberImpl[] result = new MemberImpl[list.size()];
+            list.toArray(result);
+            for( int j=0; j<result.length; j++) {
+                removeMember(result[j]);
+            }
+            return result;
+        } else {
+            return EMPTY_MEMBERS ;
+        }
+    }
+
+    /**
+     * Returning that service has members or not
+     */
+    public boolean hasMembers() {
+        return members.length > 0 ;
+    }
+    
+    
+    public MemberImpl getMember(Member mbr) {
+        if(hasMembers()) {
+            MemberImpl result = null;
+            for ( int i=0; i<this.members.length && result==null; i++ ) {
+                if ( members[i].equals(mbr) ) result = members[i];
+            }//for
+            return result;
+        } else {
+            return null;
+        }
+    }
+    
+    public boolean contains(Member mbr) { 
+        return getMember(mbr)!=null;
+    }
+ 
+    /**
+     * Returning a list of all the members in the membership
+     * We not need a copy: add and remove generate new arrays.
+     */
+    public MemberImpl[] getMembers() {
+        if(hasMembers()) {
+            return members;
+        } else {
+            return EMPTY_MEMBERS;
+        }
+    }
+
+    /**
+     * get a copy from all member entries
+     */
+    protected synchronized MbrEntry[] getMemberEntries()
+    {
+        MbrEntry[] result = new MbrEntry[map.size()];
+        Iterator<Map.Entry<MemberImpl,MbrEntry>> i = map.entrySet().iterator();
+        int pos = 0;
+        while ( i.hasNext() )
+            result[pos++] = i.next().getValue();
+        return result;
+    }
+    
+    // --------------------------------------------- Inner Class
+
+    private static class MemberComparator implements Comparator<Member>,
+            Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public int compare(Member m1, Member m2) {
+            //longer alive time, means sort first
+            long result = m2.getMemberAliveTime() - m1.getMemberAliveTime();
+            if (result < 0)
+                return -1;
+            else if (result == 0)
+                return 0;
+            else
+                return 1;
+        }
+    }
+    
+    /**
+     * Inner class that represents a member entry
+     */
+    protected static class MbrEntry
+    {
+
+        protected MemberImpl mbr;
+        protected long lastHeardFrom;
+
+        public MbrEntry(MemberImpl mbr) {
+           this.mbr = mbr;
+        }
+
+        /**
+         * Indicate that this member has been accessed.
+         */
+        public void accessed(){
+           lastHeardFrom = System.currentTimeMillis();
+        }
+
+        /**
+         * Return the actual Member object
+         */
+        public MemberImpl getMember() {
+            return mbr;
+        }
+
+        /**
+         * Check if this dude has expired
+         * @param maxtime The time threshold
+         */
+        public boolean hasExpired(long maxtime) {
+            long delta = System.currentTimeMillis() - lastHeardFrom;
+            return delta > maxtime;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/StaticMember.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/StaticMember.java
new file mode 100644
index 0000000..fc46638
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/StaticMember.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.membership;
+
+import java.io.IOException;
+
+import org.apache.catalina.tribes.util.Arrays;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class StaticMember extends MemberImpl {
+    public StaticMember() {
+        super();
+    }
+
+    public StaticMember(String host, int port, long aliveTime) throws IOException {
+        super(host, port, aliveTime);
+    }
+
+    public StaticMember(String host, int port, long aliveTime, byte[] payload) throws IOException {
+        super(host, port, aliveTime, payload);
+    }
+     
+    /**
+     * @param host String, either in byte array string format, like {214,116,1,3}
+     * or as a regular hostname, 127.0.0.1 or tomcat01.mydomain.com
+     */
+    public void setHost(String host) {
+        if ( host == null ) return;
+        if ( host.startsWith("{") ) setHost(Arrays.fromString(host));
+        else try { setHostname(host); }catch (IOException x) { throw new RuntimeException(x);}
+        
+    }
+    
+    /**
+     * @param domain String, either in byte array string format, like {214,116,1,3}
+     * or as a regular string value like 'mydomain'. The latter will be converted using ISO-8859-1 encoding
+     */
+    public void setDomain(String domain) {
+        if ( domain == null ) return;
+        if ( domain.startsWith("{") ) setDomain(Arrays.fromString(domain));
+        else setDomain(Arrays.convert(domain));
+    }
+    
+    /**
+     * @param id String, must be in byte array string format, like {214,116,1,3} and exactly 16 bytes long
+     */
+    public void setUniqueId(String id) {
+        byte[] uuid = Arrays.fromString(id);
+        if ( uuid==null || uuid.length != 16 ) throw new RuntimeException("UUID must be exactly 16 bytes, not:"+id);
+        setUniqueId(uuid);
+    }
+    
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/mbeans-descriptors.xml
new file mode 100644
index 0000000..3243734
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/membership/mbeans-descriptors.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!DOCTYPE mbeans-descriptors PUBLIC
+   "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+   "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+<mbeans-descriptors>
+  <mbean
+    name="McastService"
+    description="Cluster Membership service implementation"
+    domain="Catalina"
+    group="Cluster"
+    type="org.apache.catalina.tribes.membership.McastService">
+    <attribute
+      name="address"
+      description="Multicast IP Address"
+      type="java.lang.String"/>
+    <attribute
+      name="bind"
+      description="Multicast IP Interface address (default auto)"
+      type="java.lang.String"/>
+    <attribute
+      name="dropTime"
+      description="Timeout from frequency ping after member disapper notify"
+      type="long"/>
+    <attribute
+      name="frequency"
+      description="Ping Frequency at msec"
+      type="long"/>
+    <attribute
+      name="info"
+      description="Class version info"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="localLoopbackDisabled"
+      description="Is local loopback disabled?"
+      is="true"
+      type="boolean"/>
+    <attribute
+      name="localMemberName"
+      description="Complete local receiver information"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="membersByName"
+      description="Complete remote sender information"
+      type="[Ljava.lang.String;"
+      writeable="false"/>
+    <attribute
+      name="port"
+      description="Multicast UDP Port"
+      type="int"/>
+    <attribute
+      name="recoveryCounter"
+      description="Counter after membership failure socket restarted"
+      type="int"/>
+    <attribute
+      name="recoveryEnabled"
+      description="Membership recovery enabled"
+      is="true"
+      type="boolean"/>
+    <attribute
+      name="recoverySleepTime"
+      description="Sleep time between next socket recovery (5000 msec)"
+      type="long"/>
+    <attribute
+      name="soTimeout"
+      description="Multicast Socket Timeout"
+      type="int"/>
+    <attribute
+      name="Ttl"
+      description=""
+      type="int"/>
+    <operation
+      name="start"
+      description="Start the cluster membership"
+      impact="ACTION"
+      returnType="void"/>
+    <operation
+      name="stop"
+      description="Stop the cluster membership"
+      impact="ACTION"
+      returnType="void"/>
+  </mbean>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/package.html
new file mode 100644
index 0000000..54b5e0b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/package.html
@@ -0,0 +1,86 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<body>
+<head><title>Apache Tribes - The Tomcat Cluster Communication Module</title>
+<h3>QuickStart</h3>
+    <pre><code>
+            //create a channel
+            Channel myChannel = new GroupChannel();
+
+            //create my listeners
+            MyMessageListener msgListener = new MyMessageListener();
+            MyMemberListener mbrListener = new MyMemberListener();
+
+            //attach the listeners to the channel
+            myChannel.addMembershipListener(mbrListener);
+            myChannel.addChannelListener(msgListener);
+
+            //start the channel
+            myChannel.start(Channel.DEFAULT);
+
+            //create a message to be sent, message must implement java.io.Serializable
+            //for performance reasons you probably want them to implement java.io.Externalizable
+            Serializable myMsg = new MyMessage();
+
+            //retrieve my current members
+            Member[] group = myChannel.getMembers();
+
+            //send the message
+            channel.send(group,myMsg,Channel.SEND_OPTIONS_DEFAULT);
+
+    </code></pre>
+<h3>Interfaces for the Application Developer</h3>
+    <ol>
+        <li><code>org.apache.catalina.tribes.Channel</code>
+            Main component to interact with to send messages
+        </li>
+        <li><code>org.apache.catalina.tribes.MembershipListener</code>
+            Listen to membership changes
+        </li>
+        <li><code>org.apache.catalina.tribes.ChannelListener</code>
+            Listen to data messages
+        </li>
+        <li><code>org.apache.catalina.tribes.Member</code>
+            Identifies a node, implementation specific, default is org.apache.catalina.tribes.membership.MemberImpl
+        </li>
+    </ol>
+    <h3>Interfaces for the Tribes Component Developer</h3>
+    <ol>
+        <li><code>org.apache.catalina.tribes.Channel</code>
+            Main component to that the application interacts with
+        </li>
+        <li><code>org.apache.catalina.tribes.ChannelReceiver</code>
+            IO Component to receive messages over some network transport
+        </li>
+        <li><code>org.apache.catalina.tribes.ChannelSender</code>
+            IO Component to send messages over some network transport
+        </li>
+        <li><code>org.apache.catalina.tribes.MembershipService</code>
+            IO Component that handles membership discovery and 
+        </li>
+        <li><code>org.apache.catalina.tribes.ChannelInterceptor</code>
+            interceptors between the Channel and the IO layer
+        </li>
+        <li><code>org.apache.catalina.tribes.ChannelMessage</code>
+            The message that is sent through the interceptor stack down to the IO layer 
+        </li>
+
+        <li><code>org.apache.catalina.tribes.Member</code>
+            Identifies a node, implementation specific to the underlying IO logic
+        </li>
+    </ol>
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
new file mode 100644
index 0000000..d1ef0b3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
@@ -0,0 +1,1519 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.tipis;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelException.FaultyMember;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Heartbeat;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.group.Response;
+import org.apache.catalina.tribes.group.RpcCallback;
+import org.apache.catalina.tribes.group.RpcChannel;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements RpcCallback, ChannelListener, MembershipListener, Heartbeat {
+    private static final long serialVersionUID = 1L;
+
+    private static final Log log = LogFactory.getLog(AbstractReplicatedMap.class);
+
+    /**
+     * The default initial capacity - MUST be a power of two.
+     */
+    public static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+    /**
+     * The load factor used when none specified in constructor.
+     **/
+    public static final float DEFAULT_LOAD_FACTOR = 0.75f;
+    
+    /**
+     * Used to identify the map
+     */
+    final String chset = "ISO-8859-1";
+
+//------------------------------------------------------------------------------
+//              INSTANCE VARIABLES
+//------------------------------------------------------------------------------
+    protected abstract int getStateMessageType();
+    
+    
+    /**
+     * Timeout for RPC messages, how long we will wait for a reply
+     */
+    protected transient long rpcTimeout = 5000;
+    /**
+     * Reference to the channel for sending messages
+     */
+    protected transient Channel channel;
+    /**
+     * The RpcChannel to send RPC messages through
+     */
+    protected transient RpcChannel rpcChannel;
+    /**
+     * The Map context name makes this map unique, this
+     * allows us to have more than one map shared
+     * through one channel
+     */
+    protected transient byte[] mapContextName;
+    /**
+     * Has the state been transferred
+     */
+    protected transient boolean stateTransferred = false;
+    /**
+     * Simple lock object for transfers
+     */
+    protected transient Object stateMutex = new Object();
+    /**
+     * A list of members in our map
+     */
+    protected transient HashMap<Member, Long> mapMembers = new HashMap<Member, Long>();
+    /**
+     * Our default send options
+     */
+    protected transient int channelSendOptions = Channel.SEND_OPTIONS_DEFAULT;
+    /**
+     * The owner of this map, ala a SessionManager for example
+     */
+    protected transient MapOwner mapOwner;
+    /**
+     * External class loaders if serialization and deserialization is to be performed successfully.
+     */
+    protected transient ClassLoader[] externalLoaders;
+    
+    /**
+     * The node we are currently backing up data to, this index will rotate
+     * on a round robin basis
+     */
+    protected transient int currentNode = 0;
+    
+    /**
+     * Since the map keeps internal membership
+     * this is the timeout for a ping message to be responded to
+     * If a remote map doesn't respond within this timeframe, 
+     * its considered dead.
+     */
+    protected transient long accessTimeout = 5000;
+    
+    /**
+     * Readable string of the mapContextName value
+     */
+    protected transient String mapname = "";
+
+//------------------------------------------------------------------------------
+//              map owner interface
+//------------------------------------------------------------------------------
+    
+    public static interface MapOwner {
+        // a typo, should have been "objectMadePrimary"
+        public void objectMadePrimay(Object key, Object value);
+    }
+
+//------------------------------------------------------------------------------
+//              CONSTRUCTORS
+//------------------------------------------------------------------------------
+
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     * @param initialCapacity int - the size of this map, see HashMap
+     * @param loadFactor float - load factor, see HashMap
+     * @param cls - a list of classloaders to be used for deserialization of objects.
+     */
+    public AbstractReplicatedMap(MapOwner owner,
+                                 Channel channel, 
+                                 long timeout, 
+                                 String mapContextName, 
+                                 int initialCapacity,
+                                 float loadFactor,
+                                 int channelSendOptions,
+                                 ClassLoader[] cls) {
+        super(initialCapacity, loadFactor, 15);
+        init(owner, channel, mapContextName, timeout, channelSendOptions, cls);
+        
+    }
+
+    /**
+     * Helper methods, wraps a single member in an array
+     * @param m Member
+     * @return Member[]
+     */
+    protected Member[] wrap(Member m) {
+        if ( m == null ) return new Member[0];
+        else return new Member[] {m};
+    }
+
+    /**
+     * Initializes the map by creating the RPC channel, registering itself as a channel listener
+     * This method is also responsible for initiating the state transfer
+     * @param owner Object
+     * @param channel Channel
+     * @param mapContextName String
+     * @param timeout long
+     * @param channelSendOptions int
+     * @param cls ClassLoader[]
+     */
+    protected void init(MapOwner owner, Channel channel, String mapContextName, long timeout, int channelSendOptions,ClassLoader[] cls) {
+        log.info("Initializing AbstractReplicatedMap with context name:"+mapContextName);
+        this.mapOwner = owner;
+        this.externalLoaders = cls;
+        this.channelSendOptions = channelSendOptions;
+        this.channel = channel;
+        this.rpcTimeout = timeout;
+
+        try {
+            this.mapname = mapContextName;
+            //unique context is more efficient if it is stored as bytes
+            this.mapContextName = mapContextName.getBytes(chset);
+        } catch (UnsupportedEncodingException x) {
+            log.warn("Unable to encode mapContextName[" + mapContextName + "] using getBytes(" + chset +") using default getBytes()", x);
+            this.mapContextName = mapContextName.getBytes();
+        }
+        if ( log.isTraceEnabled() ) log.trace("Created Lazy Map with name:"+mapContextName+", bytes:"+Arrays.toString(this.mapContextName));
+
+        //create an rpc channel and add the map as a listener
+        this.rpcChannel = new RpcChannel(this.mapContextName, channel, this);
+        //add this map as a message listener
+        this.channel.addChannelListener(this);
+        //listen for membership notifications
+        this.channel.addMembershipListener(this);
+        
+        
+        try {
+            //broadcast our map, this just notifies other members of our existence
+            broadcast(MapMessage.MSG_INIT, true);
+            //transfer state from another map
+            transferState();
+            //state is transferred, we are ready for messaging
+            broadcast(MapMessage.MSG_START, true);
+        } catch (ChannelException x) {
+            log.warn("Unable to send map start message.");
+            // remove listener from channel
+            this.rpcChannel.breakdown();
+            this.channel.removeChannelListener(this);
+            this.channel.removeMembershipListener(this);
+            throw new RuntimeException("Unable to start replicated map.",x);
+        }
+    }
+    
+    
+    /**
+     * Sends a ping out to all the members in the cluster, not just map members
+     * that this map is alive.
+     * @param timeout long
+     * @throws ChannelException
+     */
+    protected void ping(long timeout) throws ChannelException {
+        //send out a map membership message, only wait for the first reply
+        MapMessage msg = new MapMessage(this.mapContextName, 
+                                        MapMessage.MSG_INIT,
+                                        false, 
+                                        null, 
+                                        null, 
+                                        null, 
+                                        channel.getLocalMember(false),
+                                        null);
+        if ( channel.getMembers().length > 0 ) {
+            try {
+                //send a ping, wait for all nodes to reply
+                Response[] resp = rpcChannel.send(channel.getMembers(), 
+                                                  msg, RpcChannel.ALL_REPLY, 
+                                                  (channelSendOptions),
+                                                  (int) accessTimeout);
+                for (int i = 0; i < resp.length; i++) {
+                    memberAlive(resp[i].getSource());
+                }
+            } catch (ChannelException ce) {
+                // Handle known failed members
+                FaultyMember[] faultyMembers = ce.getFaultyMembers();
+                for (FaultyMember faultyMember : faultyMembers) {
+                    memberDisappeared(faultyMember.getMember());
+                }
+            }            
+        }
+        //update our map of members, expire some if we didn't receive a ping back
+        synchronized (mapMembers) {
+            Iterator<Map.Entry<Member, Long>> it = mapMembers.entrySet().iterator();
+            long now = System.currentTimeMillis();
+            while ( it.hasNext() ) {
+                Map.Entry<Member,Long> entry = it.next();
+                long access = entry.getValue().longValue(); 
+                if ( (now - access) > timeout ) {
+                    it.remove();
+                    memberDisappeared(entry.getKey());
+                }
+            }
+        }//synch
+    }
+
+    /**
+     * We have received a member alive notification
+     * @param member Member
+     */
+    protected void memberAlive(Member member) {
+        synchronized (mapMembers) {
+            if (!mapMembers.containsKey(member)) {
+                mapMemberAdded(member);
+            } //end if
+            mapMembers.put(member, new Long(System.currentTimeMillis()));
+        }
+    }
+    
+    /**
+     * Helper method to broadcast a message to all members in a channel
+     * @param msgtype int
+     * @param rpc boolean
+     * @throws ChannelException
+     */
+    protected void broadcast(int msgtype, boolean rpc) throws ChannelException {
+        //send out a map membership message, only wait for the first reply
+        MapMessage msg = new MapMessage(this.mapContextName, msgtype,
+                                        false, null, null, null, channel.getLocalMember(false), null);
+        if ( rpc) {
+            Response[] resp = rpcChannel.send(channel.getMembers(), msg, RpcChannel.FIRST_REPLY, (channelSendOptions),rpcTimeout);
+            for (int i = 0; i < resp.length; i++) {
+                mapMemberAdded(resp[i].getSource());
+                messageReceived(resp[i].getMessage(), resp[i].getSource());
+            }
+        } else {
+            channel.send(channel.getMembers(),msg,channelSendOptions);
+        }
+    }
+
+    public void breakdown() {
+        finalize();
+    }
+
+    @Override
+    public void finalize() {
+        try {broadcast(MapMessage.MSG_STOP,false); }catch ( Exception ignore){}
+        //cleanup
+        if (this.rpcChannel != null) {
+            this.rpcChannel.breakdown();
+        }
+        if (this.channel != null) {
+            this.channel.removeChannelListener(this);
+            this.channel.removeMembershipListener(this);
+        }
+        this.rpcChannel = null;
+        this.channel = null;
+        this.mapMembers.clear();
+        super.clear();
+        this.stateTransferred = false;
+        this.externalLoaders = null;
+    }
+    
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(this.mapContextName);
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if ( !(o instanceof AbstractReplicatedMap)) return false;
+        if ( !(o.getClass().equals(this.getClass())) ) return false;
+        AbstractReplicatedMap other = (AbstractReplicatedMap)o;
+        return Arrays.equals(mapContextName,other.mapContextName);
+    }
+
+//------------------------------------------------------------------------------
+//              GROUP COM INTERFACES
+//------------------------------------------------------------------------------
+    public Member[] getMapMembers(HashMap<Member, Long> members) {
+        synchronized (members) {
+            Member[] result = new Member[members.size()];
+            members.keySet().toArray(result);
+            return result;
+        }
+    }
+    public Member[] getMapMembers() {
+        return getMapMembers(this.mapMembers);
+    }
+    
+    public Member[] getMapMembersExcl(Member[] exclude) {
+        synchronized (mapMembers) {
+            @SuppressWarnings("unchecked") // mapMembers has the correct type
+            HashMap<Member, Long> list = (HashMap<Member, Long>)mapMembers.clone();
+            for (int i=0; i<exclude.length;i++) list.remove(exclude[i]);
+            return getMapMembers(list);
+        }
+    }
+
+
+    /**
+     * Replicates any changes to the object since the last time
+     * The object has to be primary, ie, if the object is a proxy or a backup, it will not be replicated<br>
+     * @param complete - if set to true, the object is replicated to its backup
+     * if set to false, only objects that implement ReplicatedMapEntry and the isDirty() returns true will
+     * be replicated
+     */
+    public void replicate(Object key, boolean complete) {
+        if ( log.isTraceEnabled() )
+            log.trace("Replicate invoked on key:"+key);
+        MapEntry entry = (MapEntry)super.get(key);
+        if ( entry == null ) return;
+        if ( !entry.isSerializable() ) return;
+        if (entry.isPrimary() && entry.getBackupNodes()!= null && entry.getBackupNodes().length > 0) {
+            Object value = entry.getValue();
+            //check to see if we need to replicate this object isDirty()||complete
+            boolean repl = complete || ( (value instanceof ReplicatedMapEntry) && ( (ReplicatedMapEntry) value).isDirty());
+            
+            if (!repl) {
+                if ( log.isTraceEnabled() )
+                    log.trace("Not replicating:"+key+", no change made");
+                
+                return;
+            }
+            //check to see if the message is diffable
+            boolean diff = ( (value instanceof ReplicatedMapEntry) && ( (ReplicatedMapEntry) value).isDiffable());
+            MapMessage msg = null;
+            if (diff) {
+                ReplicatedMapEntry rentry = (ReplicatedMapEntry)entry.getValue();
+                try {
+                    rentry.lock();
+                    //construct a diff message
+                    msg = new MapMessage(mapContextName, MapMessage.MSG_BACKUP,
+                                         true, (Serializable) entry.getKey(), null,
+                                         rentry.getDiff(),
+                                         entry.getPrimary(),
+                                         entry.getBackupNodes());
+                    rentry.resetDiff();
+                } catch (IOException x) {
+                    log.error("Unable to diff object. Will replicate the entire object instead.", x);
+                } finally {
+                    rentry.unlock();
+                }
+                
+            }
+            if (msg == null) {
+                //construct a complete
+                msg = new MapMessage(mapContextName, MapMessage.MSG_BACKUP,
+                                     false, (Serializable) entry.getKey(),
+                                     (Serializable) entry.getValue(),
+                                     null, entry.getPrimary(),entry.getBackupNodes());
+
+            }
+            try {
+                if ( channel!=null && entry.getBackupNodes()!= null && entry.getBackupNodes().length > 0 ) {
+                    channel.send(entry.getBackupNodes(), msg, channelSendOptions);
+                }
+            } catch (ChannelException x) {
+                log.error("Unable to replicate data.", x);
+            }
+        } //end if
+
+    }
+
+    /**
+     * This can be invoked by a periodic thread to replicate out any changes.
+     * For maps that don't store objects that implement ReplicatedMapEntry, this
+     * method should be used infrequently to avoid large amounts of data transfer
+     * @param complete boolean
+     */
+    public void replicate(boolean complete) {
+        Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+        while (i.hasNext()) {
+            Map.Entry<?,?> e = i.next();
+            replicate(e.getKey(), complete);
+        } //while
+
+    }
+
+    public void transferState() {
+        try {
+            Member[] members = getMapMembers();
+            Member backup = members.length > 0 ? (Member) members[0] : null;
+            if (backup != null) {
+                MapMessage msg = new MapMessage(mapContextName, getStateMessageType(), false,
+                                                null, null, null, null, null);
+                Response[] resp = rpcChannel.send(new Member[] {backup}, msg, RpcChannel.FIRST_REPLY, channelSendOptions, rpcTimeout);
+                if (resp.length > 0) {
+                    synchronized (stateMutex) {
+                        msg = (MapMessage) resp[0].getMessage();
+                        msg.deserialize(getExternalLoaders());
+                        ArrayList<?> list = (ArrayList<?>) msg.getValue();
+                        for (int i = 0; i < list.size(); i++) {
+                            messageReceived( (Serializable) list.get(i), resp[0].getSource());
+                        } //for
+                    }
+                } else {
+                    log.warn("Transfer state, 0 replies, probably a timeout.");
+                }
+            }
+        } catch (ChannelException x) {
+            log.error("Unable to transfer LazyReplicatedMap state.", x);
+        } catch (IOException x) {
+            log.error("Unable to transfer LazyReplicatedMap state.", x);
+        } catch (ClassNotFoundException x) {
+            log.error("Unable to transfer LazyReplicatedMap state.", x);
+        }
+        stateTransferred = true;
+    }
+
+    /**
+     * TODO implement state transfer
+     * @param msg Serializable
+     * @return Serializable - null if no reply should be sent
+     */
+    @Override
+    public Serializable replyRequest(Serializable msg, final Member sender) {
+        if (! (msg instanceof MapMessage))return null;
+        MapMessage mapmsg = (MapMessage) msg;
+
+        //map init request
+        if (mapmsg.getMsgType() == MapMessage.MSG_INIT) {
+            mapmsg.setPrimary(channel.getLocalMember(false));
+            return mapmsg;
+        }
+        
+        //map start request
+        if (mapmsg.getMsgType() == MapMessage.MSG_START) {
+            mapmsg.setPrimary(channel.getLocalMember(false));
+            mapMemberAdded(sender);
+            return mapmsg;
+        }
+
+        //backup request
+        if (mapmsg.getMsgType() == MapMessage.MSG_RETRIEVE_BACKUP) {
+            MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+            if (entry == null || (!entry.isSerializable()) )return null;
+            mapmsg.setValue( (Serializable) entry.getValue());
+            return mapmsg;
+        }
+
+        //state transfer request
+        if (mapmsg.getMsgType() == MapMessage.MSG_STATE || mapmsg.getMsgType() == MapMessage.MSG_STATE_COPY) {
+            synchronized (stateMutex) { //make sure we dont do two things at the same time
+                ArrayList<MapMessage> list = new ArrayList<MapMessage>();
+                Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+                while (i.hasNext()) {
+                    Map.Entry<?,?> e = i.next();
+                    MapEntry entry = (MapEntry) super.get(e.getKey());
+                    if ( entry != null && entry.isSerializable() ) {
+                        boolean copy = (mapmsg.getMsgType() == MapMessage.MSG_STATE_COPY);
+                        MapMessage me = new MapMessage(mapContextName, 
+                                                       copy?MapMessage.MSG_COPY:MapMessage.MSG_PROXY,
+                            false, (Serializable) entry.getKey(), copy?(Serializable) entry.getValue():null, null, entry.getPrimary(),entry.getBackupNodes());
+                        list.add(me);
+                    }
+                }
+                mapmsg.setValue(list);
+                return mapmsg;
+                
+            } //synchronized
+        }
+
+        return null;
+
+    }
+
+    /**
+     * If the reply has already been sent to the requesting thread,
+     * the rpc callback can handle any data that comes in after the fact.
+     * @param msg Serializable
+     * @param sender Member
+     */
+    @Override
+    public void leftOver(Serializable msg, Member sender) {
+        //left over membership messages
+        if (! (msg instanceof MapMessage))return;
+
+        MapMessage mapmsg = (MapMessage) msg;
+        try {
+            mapmsg.deserialize(getExternalLoaders());
+            if (mapmsg.getMsgType() == MapMessage.MSG_START) {
+                mapMemberAdded(mapmsg.getPrimary());
+            } else if (mapmsg.getMsgType() == MapMessage.MSG_INIT) {
+                memberAlive(mapmsg.getPrimary());
+            }
+        } catch (IOException x ) {
+            log.error("Unable to deserialize MapMessage.",x);
+        } catch (ClassNotFoundException x ) {
+            log.error("Unable to deserialize MapMessage.",x);
+        }
+    }
+
+    @Override
+    public void messageReceived(Serializable msg, Member sender) {
+        if (! (msg instanceof MapMessage)) return;
+
+        MapMessage mapmsg = (MapMessage) msg;
+        if ( log.isTraceEnabled() ) {
+            log.trace("Map["+mapname+"] received message:"+mapmsg);
+        }
+        
+        try {
+            mapmsg.deserialize(getExternalLoaders());
+        } catch (IOException x) {
+            log.error("Unable to deserialize MapMessage.", x);
+            return;
+        } catch (ClassNotFoundException x) {
+            log.error("Unable to deserialize MapMessage.", x);
+            return;
+        }
+        if ( log.isTraceEnabled() ) 
+            log.trace("Map message received from:"+sender.getName()+" msg:"+mapmsg);
+        if (mapmsg.getMsgType() == MapMessage.MSG_START) {
+            mapMemberAdded(mapmsg.getPrimary());
+        }
+
+        if (mapmsg.getMsgType() == MapMessage.MSG_STOP) {
+            memberDisappeared(mapmsg.getPrimary());
+        }
+
+        if (mapmsg.getMsgType() == MapMessage.MSG_PROXY) {
+            MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+            if ( entry==null ) {
+                entry = new MapEntry(mapmsg.getKey(), mapmsg.getValue());
+                entry.setBackup(false);
+                entry.setProxy(true);
+                entry.setBackupNodes(mapmsg.getBackupNodes());
+                entry.setPrimary(mapmsg.getPrimary());
+                super.put(entry.getKey(), entry);
+            } else {
+                entry.setProxy(true);
+                entry.setBackup(false);
+                entry.setBackupNodes(mapmsg.getBackupNodes());
+                entry.setPrimary(mapmsg.getPrimary());
+            }
+        }
+
+        if (mapmsg.getMsgType() == MapMessage.MSG_REMOVE) {
+            super.remove(mapmsg.getKey());
+        }
+
+        if (mapmsg.getMsgType() == MapMessage.MSG_BACKUP || mapmsg.getMsgType() == MapMessage.MSG_COPY) {
+            MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+            if (entry == null) {
+                entry = new MapEntry(mapmsg.getKey(), mapmsg.getValue());
+                entry.setBackup(mapmsg.getMsgType() == MapMessage.MSG_BACKUP);
+                entry.setProxy(false);
+                entry.setBackupNodes(mapmsg.getBackupNodes());
+                entry.setPrimary(mapmsg.getPrimary());
+                if (mapmsg.getValue()!=null && mapmsg.getValue() instanceof ReplicatedMapEntry ) {
+                    ((ReplicatedMapEntry)mapmsg.getValue()).setOwner(getMapOwner());
+                }
+            } else {
+                entry.setBackup(mapmsg.getMsgType() == MapMessage.MSG_BACKUP);
+                entry.setProxy(false);
+                entry.setBackupNodes(mapmsg.getBackupNodes());
+                entry.setPrimary(mapmsg.getPrimary());
+                if (entry.getValue() instanceof ReplicatedMapEntry) {
+                    ReplicatedMapEntry diff = (ReplicatedMapEntry) entry.getValue();
+                    if (mapmsg.isDiff()) {
+                        try {
+                            diff.lock();
+                            diff.applyDiff(mapmsg.getDiffValue(), 0, mapmsg.getDiffValue().length);
+                        } catch (Exception x) {
+                            log.error("Unable to apply diff to key:" + entry.getKey(), x);
+                        } finally {
+                            diff.unlock();
+                        }
+                    } else {
+                        if ( mapmsg.getValue()!=null ) entry.setValue(mapmsg.getValue());
+                        ((ReplicatedMapEntry)entry.getValue()).setOwner(getMapOwner());
+                    } //end if
+                } else if  (mapmsg.getValue() instanceof ReplicatedMapEntry) {
+                    ReplicatedMapEntry re = (ReplicatedMapEntry)mapmsg.getValue();
+                    re.setOwner(getMapOwner());
+                    entry.setValue(re);
+                } else {
+                    if ( mapmsg.getValue()!=null ) entry.setValue(mapmsg.getValue());
+                } //end if
+            } //end if
+            super.put(entry.getKey(), entry);
+        } //end if
+    }
+
+    @Override
+    public boolean accept(Serializable msg, Member sender) {
+        boolean result = false;
+        if (msg instanceof MapMessage) {
+            if ( log.isTraceEnabled() ) log.trace("Map["+mapname+"] accepting...."+msg);
+            result = Arrays.equals(mapContextName, ( (MapMessage) msg).getMapId());
+            if ( log.isTraceEnabled() ) log.trace("Msg["+mapname+"] accepted["+result+"]...."+msg);
+        }
+        return result;
+    }
+
+    public void mapMemberAdded(Member member) {
+        if ( member.equals(getChannel().getLocalMember(false)) ) return;
+        boolean memberAdded = false;
+        //select a backup node if we don't have one
+        synchronized (mapMembers) {
+            if (!mapMembers.containsKey(member) ) {
+                mapMembers.put(member, new Long(System.currentTimeMillis()));
+                memberAdded = true;
+            }
+        }
+        if ( memberAdded ) {
+            synchronized (stateMutex) {
+                Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+                while (i.hasNext()) {
+                    Map.Entry<?,?> e = i.next();
+                    MapEntry entry = (MapEntry) super.get(e.getKey());
+                    if ( entry == null ) continue;
+                    if (entry.isPrimary() && (entry.getBackupNodes() == null || entry.getBackupNodes().length == 0)) {
+                        try {
+                            Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+                            entry.setBackupNodes(backup);
+                            entry.setPrimary(channel.getLocalMember(false));
+                        } catch (ChannelException x) {
+                            log.error("Unable to select backup node.", x);
+                        } //catch
+                    } //end if
+                } //while
+            } //synchronized
+        }//end if
+    }
+    
+    public boolean inSet(Member m, Member[] set) {
+        if ( set == null ) return false;
+        boolean result = false;
+        for (int i=0; i<set.length && (!result); i++ )
+            if ( m.equals(set[i]) ) result = true;
+        return result;
+    }
+
+    public Member[] excludeFromSet(Member[] mbrs, Member[] set) {
+        ArrayList<Member> result = new ArrayList<Member>();
+        for (int i=0; i<set.length; i++ ) {
+            boolean include = true;
+            for (int j=0; j<mbrs.length; j++ ) 
+                if ( mbrs[j].equals(set[i]) ) include = false;
+            if ( include ) result.add(set[i]);
+        }
+        return result.toArray(new Member[result.size()]);
+    }
+
+    @Override
+    public void memberAdded(Member member) {
+        //do nothing
+    }
+
+    @Override
+    public void memberDisappeared(Member member) {
+        boolean removed = false;
+        synchronized (mapMembers) {
+            removed = (mapMembers.remove(member) != null );
+            if (!removed) {
+                if (log.isDebugEnabled()) log.debug("Member["+member+"] disappeared, but was not present in the map.");
+                return; //the member was not part of our map.
+            }
+        }
+        
+        Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+        while (i.hasNext()) {
+            Map.Entry<?,?> e = i.next();
+            MapEntry entry = (MapEntry) super.get(e.getKey());
+            if (entry==null) continue;
+            if (entry.isPrimary() && inSet(member,entry.getBackupNodes())) {
+                if (log.isDebugEnabled()) log.debug("[1] Primary choosing a new backup");
+                try {
+                    Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+                    entry.setBackupNodes(backup);
+                    entry.setPrimary(channel.getLocalMember(false));
+                } catch (ChannelException x) {
+                    log.error("Unable to relocate[" + entry.getKey() + "] to a new backup node", x);
+                }
+            } else if (member.equals(entry.getPrimary())) {
+                if (log.isDebugEnabled()) log.debug("[2] Primary disappeared");
+                entry.setPrimary(null);
+            } //end if
+            
+            if ( entry.isProxy() &&
+                 entry.getPrimary() == null && 
+                 entry.getBackupNodes()!=null && 
+                 entry.getBackupNodes().length == 1 &&
+                 entry.getBackupNodes()[0].equals(member) ) {
+                //remove proxies that have no backup nor primaries
+                if (log.isDebugEnabled()) log.debug("[3] Removing orphaned proxy");
+                i.remove();
+            } else if ( entry.getPrimary() == null &&
+                        entry.isBackup() &&
+                        entry.getBackupNodes()!=null && 
+                        entry.getBackupNodes().length == 1 &&
+                        entry.getBackupNodes()[0].equals(channel.getLocalMember(false)) ) {
+                try {
+                    if (log.isDebugEnabled()) log.debug("[4] Backup becoming primary");
+                    entry.setPrimary(channel.getLocalMember(false));
+                    entry.setBackup(false);
+                    entry.setProxy(false);
+                    Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+                    entry.setBackupNodes(backup);
+                    if ( mapOwner!=null ) mapOwner.objectMadePrimay(entry.getKey(),entry.getValue());
+                    
+                } catch (ChannelException x) {
+                    log.error("Unable to relocate[" + entry.getKey() + "] to a new backup node", x);
+                }
+            }
+
+        } //while
+    }
+
+    public int getNextBackupIndex() {
+        int size = mapMembers.size();
+        if (mapMembers.size() == 0)return -1;
+        int node = currentNode++;
+        if (node >= size) {
+            node = 0;
+            currentNode = 0;
+        }
+        return node;
+    }
+    public Member getNextBackupNode() {
+        Member[] members = getMapMembers();
+        int node = getNextBackupIndex();
+        if ( members.length == 0 || node==-1) return null;
+        if ( node >= members.length ) node = 0;
+        return members[node];
+    }
+
+    protected abstract Member[] publishEntryInfo(Object key, Object value) throws ChannelException;
+    
+    @Override
+    public void heartbeat() {
+        try {
+            ping(accessTimeout);
+        }catch ( Exception x ) {
+            log.error("Unable to send AbstractReplicatedMap.ping message",x);
+        }
+    }
+
+//------------------------------------------------------------------------------    
+//              METHODS TO OVERRIDE    
+//------------------------------------------------------------------------------
+  
+    /**
+     * Removes an object from this map, it will also remove it from 
+     * 
+     * @param key Object
+     * @return Object
+     */
+    @Override
+    public Object remove(Object key) {
+        return remove(key,true);
+    }
+    public Object remove(Object key, boolean notify) {
+        MapEntry entry = (MapEntry)super.remove(key);
+
+        try {
+            if (getMapMembers().length > 0 && notify) {
+                MapMessage msg = new MapMessage(getMapContextName(), MapMessage.MSG_REMOVE, false, (Serializable) key, null, null, null,null);
+                getChannel().send(getMapMembers(), msg, getChannelSendOptions());
+            }
+        } catch ( ChannelException x ) {
+            log.error("Unable to replicate out data for a LazyReplicatedMap.remove operation",x);
+        }
+        return entry!=null?entry.getValue():null;
+    }
+    
+    public MapEntry getInternal(Object key) {
+        return (MapEntry)super.get(key);
+    }
+    
+    @Override
+    public Object get(Object key) {
+        MapEntry entry = (MapEntry)super.get(key);
+        if (log.isTraceEnabled()) log.trace("Requesting id:"+key+" entry:"+entry);
+        if ( entry == null ) return null;
+        if ( !entry.isPrimary() ) {
+            //if the message is not primary, we need to retrieve the latest value
+            try {
+                Member[] backup = null;
+                MapMessage msg = null;
+                if ( !entry.isBackup() ) {
+                    //make sure we don't retrieve from ourselves
+                    msg = new MapMessage(getMapContextName(), MapMessage.MSG_RETRIEVE_BACKUP, false,
+                                         (Serializable) key, null, null, null,null);
+                    Response[] resp = getRpcChannel().send(entry.getBackupNodes(),msg, RpcChannel.FIRST_REPLY, Channel.SEND_OPTIONS_DEFAULT, getRpcTimeout());
+                    if (resp == null || resp.length == 0) {
+                        //no responses
+                        log.warn("Unable to retrieve remote object for key:" + key);
+                        return null;
+                    }
+                    msg = (MapMessage) resp[0].getMessage();
+                    msg.deserialize(getExternalLoaders());
+                    backup = entry.getBackupNodes();
+                    if ( entry.getValue() instanceof ReplicatedMapEntry ) {
+                        ReplicatedMapEntry val = (ReplicatedMapEntry)entry.getValue();
+                        val.setOwner(getMapOwner());
+                    }
+                    if ( msg.getValue()!=null ) entry.setValue(msg.getValue());
+                }
+                if (entry.isBackup()) {
+                    //select a new backup node
+                    backup = publishEntryInfo(key, entry.getValue());
+                } else if ( entry.isProxy() ) {
+                    //invalidate the previous primary
+                    msg = new MapMessage(getMapContextName(),MapMessage.MSG_PROXY,false,(Serializable)key,null,null,channel.getLocalMember(false),backup);
+                    Member[] dest = getMapMembersExcl(backup);
+                    if ( dest!=null && dest.length >0) {
+                        getChannel().send(dest, msg, getChannelSendOptions());
+                    }
+                    if ( entry.getValue() != null && entry.getValue() instanceof ReplicatedMapEntry ) {
+                        ReplicatedMapEntry val = (ReplicatedMapEntry)entry.getValue();
+                        val.setOwner(getMapOwner());   
+                    }
+                }
+                entry.setPrimary(channel.getLocalMember(false));
+                entry.setBackupNodes(backup);
+                entry.setBackup(false);
+                entry.setProxy(false);
+                if ( getMapOwner()!=null ) getMapOwner().objectMadePrimay(key, entry.getValue());
+
+            } catch (Exception x) {
+                log.error("Unable to replicate out data for a LazyReplicatedMap.get operation", x);
+                return null;
+            }
+        }
+        if (log.isTraceEnabled()) log.trace("Requesting id:"+key+" result:"+entry.getValue());
+        return entry.getValue();
+    }    
+
+    
+    protected void printMap(String header) {
+        try {
+            System.out.println("\nDEBUG MAP:"+header);
+            System.out.println("Map["+ new String(mapContextName, chset) + ", Map Size:" + super.size());
+            Member[] mbrs = getMapMembers();
+            for ( int i=0; i<mbrs.length;i++ ) {
+                System.out.println("Mbr["+(i+1)+"="+mbrs[i].getName());
+            }
+            Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+            int cnt = 0;
+
+            while (i.hasNext()) {
+                Map.Entry<?,?> e = i.next();
+                System.out.println( (++cnt) + ". " + super.get(e.getKey()));
+            }
+            System.out.println("EndMap]\n\n");
+        }catch ( Exception ignore) {
+            ignore.printStackTrace();
+        }
+    }
+    
+    /**
+         * Returns true if the key has an entry in the map.
+         * The entry can be a proxy or a backup entry, invoking <code>get(key)</code>
+         * will make this entry primary for the group
+         * @param key Object
+         * @return boolean
+         */
+        @Override
+        public boolean containsKey(Object key) {
+            return super.containsKey(key);
+        }
+    
+        @Override
+        public Object put(Object key, Object value) {
+            return put(key,value,true);
+        }
+    
+        public Object put(Object key, Object value, boolean notify) {
+            MapEntry entry = new MapEntry(key,value);
+            entry.setBackup(false);
+            entry.setProxy(false);
+            entry.setPrimary(channel.getLocalMember(false));
+    
+            Object old = null;
+    
+            //make sure that any old values get removed
+            if ( containsKey(key) ) old = remove(key);
+            try {
+                if ( notify ) {
+                    Member[] backup = publishEntryInfo(key, value);
+                    entry.setBackupNodes(backup);
+                }
+            } catch (ChannelException x) {
+                log.error("Unable to replicate out data for a LazyReplicatedMap.put operation", x);
+            }
+            super.put(key,entry);
+            return old;
+        }
+    
+    
+        /**
+         * Copies all values from one map to this instance
+         * @param m Map
+         */
+        @Override
+        public void putAll(Map m) {
+            Iterator<Map.Entry<?,?>> i = m.entrySet().iterator();
+            while ( i.hasNext() ) {
+                Map.Entry<?,?> entry = i.next();
+                put(entry.getKey(),entry.getValue());
+            }
+        }
+        
+        @Override
+        public void clear() {
+            clear(true);
+        }
+    
+        public void clear(boolean notify) {
+            if ( notify ) {
+                //only delete active keys
+                Iterator<Object> keys = keySet().iterator();
+                while (keys.hasNext())
+                    remove(keys.next());
+            } else {
+                super.clear();
+            }
+        }
+    
+        @Override
+        public boolean containsValue(Object value) {
+            if ( value == null ) {
+                return super.containsValue(value);
+            } else {
+                Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+                while (i.hasNext()) {
+                    Map.Entry<?,?> e = i.next();
+                    MapEntry entry = (MapEntry) super.get(e.getKey());
+                    if (entry!=null && entry.isActive() && value.equals(entry.getValue())) return true;
+                }//while
+                return false;
+            }//end if
+        }
+    
+        @Override
+        public Object clone() {
+            throw new UnsupportedOperationException("This operation is not valid on a replicated map");
+        }
+    
+        /**
+         * Returns the entire contents of the map
+         * Map.Entry.getValue() will return a LazyReplicatedMap.MapEntry object containing all the information 
+         * about the object.
+         * @return Set
+         */
+        public Set entrySetFull() {
+            return super.entrySet();
+        }
+    
+        public Set keySetFull() {
+            return super.keySet();
+        }
+    
+        public int sizeFull() {
+            return super.size();
+        }
+    
+        @Override
+        public Set<MapEntry> entrySet() {
+            LinkedHashSet<MapEntry> set = new LinkedHashSet<MapEntry>(super.size());
+            Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+            while ( i.hasNext() ) {
+                Map.Entry<?,?> e = i.next();
+                Object key = e.getKey();
+                MapEntry entry = (MapEntry)super.get(key);
+                if ( entry != null && entry.isActive() ) {
+                    set.add(new MapEntry(key, entry.getValue()));
+                }
+            }
+            return Collections.unmodifiableSet(set);
+        }
+    
+        @Override
+        public Set<Object> keySet() {
+            //todo implement
+            //should only return keys where this is active.
+            LinkedHashSet<Object> set = new LinkedHashSet<Object>(super.size());
+            Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+            while ( i.hasNext() ) {
+                Map.Entry<?,?> e = i.next();
+                Object key = e.getKey();
+                MapEntry entry = (MapEntry)super.get(key);
+                if ( entry!=null && entry.isActive() ) set.add(key);
+            }
+            return Collections.unmodifiableSet(set);
+
+        }
+    
+    
+        @Override
+        public int size() {
+            //todo, implement a counter variable instead
+            //only count active members in this node
+            int counter = 0;
+            Iterator<Map.Entry<?,?>> it = super.entrySet().iterator();
+            while (it!=null && it.hasNext() ) {
+                Map.Entry<?,?> e = it.next();
+                if ( e != null ) {
+                    MapEntry entry = (MapEntry) super.get(e.getKey());
+                    if (entry!=null && entry.isActive() && entry.getValue() != null) counter++;
+                }
+            }
+            return counter;
+        }
+    
+        @Override
+        public boolean isEmpty() {
+            return size()==0;
+        }
+    
+        @Override
+        public Collection<Object> values() {
+            ArrayList<Object> values = new ArrayList<Object>();
+            Iterator<Map.Entry<?,?>> i = super.entrySet().iterator();
+            while ( i.hasNext() ) {
+                Map.Entry<?,?> e = i.next();
+                MapEntry entry = (MapEntry)super.get(e.getKey());
+                if (entry!=null && entry.isActive() && entry.getValue()!=null) values.add(entry.getValue());
+            }
+            return Collections.unmodifiableCollection(values);
+        }
+        
+
+//------------------------------------------------------------------------------
+//                Map Entry class
+//------------------------------------------------------------------------------
+    public static class MapEntry implements Map.Entry<Object,Object> {
+        private boolean backup;
+        private boolean proxy;
+        private Member[] backupNodes;
+        private Member primary;
+        private Object key;
+        private Object value;
+
+        public MapEntry(Object key, Object value) {
+            setKey(key);
+            setValue(value);
+            
+        }
+        
+        public boolean isKeySerializable() {
+            return (key == null) || (key instanceof Serializable);
+        }
+        
+        public boolean isValueSerializable() {
+            return (value==null) || (value instanceof Serializable);
+        }
+        
+        public boolean isSerializable() {
+            return isKeySerializable() && isValueSerializable();
+        }
+        
+        public boolean isBackup() {
+            return backup;
+        }
+
+        public void setBackup(boolean backup) {
+            this.backup = backup;
+        }
+
+        public boolean isProxy() {
+            return proxy;
+        }
+
+        public boolean isPrimary() {
+            return (!proxy && !backup);
+        }
+
+        public boolean isActive() {
+            return !proxy;
+        }
+        
+        public void setProxy(boolean proxy) {
+            this.proxy = proxy;
+        }
+
+        public boolean isDiffable() {
+            return (value instanceof ReplicatedMapEntry) &&
+                   ((ReplicatedMapEntry)value).isDiffable();
+        }
+
+        public void setBackupNodes(Member[] nodes) {
+            this.backupNodes = nodes;
+        }
+
+        public Member[] getBackupNodes() {
+            return backupNodes;
+        }
+        
+        public void setPrimary(Member m) {
+            primary = m;
+        }
+        
+        public Member getPrimary() {
+            return primary;
+        }
+
+        @Override
+        public Object getValue() {
+            return value;
+        }
+
+        @Override
+        public Object setValue(Object value) {
+            Object old = this.value;
+            this.value = value;
+            return old;
+        }
+
+        @Override
+        public Object getKey() {
+            return key;
+        }
+        
+        public Object setKey(Object key) {
+            Object old = this.key;
+            this.key = key;
+            return old;
+        }
+
+        @Override
+        public int hashCode() {
+            return key.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return key.equals(o);
+        }
+
+        /**
+         * apply a diff, or an entire object
+         * @param data byte[]
+         * @param offset int
+         * @param length int
+         * @param diff boolean
+         * @throws IOException
+         * @throws ClassNotFoundException
+         */
+        public void apply(byte[] data, int offset, int length, boolean diff) throws IOException, ClassNotFoundException {
+            if (isDiffable() && diff) {
+                ReplicatedMapEntry rentry = (ReplicatedMapEntry) value;
+                try {
+                    rentry.lock();
+                    rentry.applyDiff(data, offset, length);
+                } finally {
+                    rentry.unlock();
+                }
+            } else if (length == 0) {
+                value = null;
+                proxy = true;
+            } else {
+                value = XByteBuffer.deserialize(data, offset, length);
+            }
+        }
+        
+        @Override
+        public String toString() {
+            StringBuilder buf = new StringBuilder("MapEntry[key:");
+            buf.append(getKey()).append("; ");
+            buf.append("value:").append(getValue()).append("; ");
+            buf.append("primary:").append(isPrimary()).append("; ");
+            buf.append("backup:").append(isBackup()).append("; ");
+            buf.append("proxy:").append(isProxy()).append(";]");
+            return buf.toString();
+        }
+
+    }
+
+//------------------------------------------------------------------------------
+//                map message to send to and from other maps
+//------------------------------------------------------------------------------
+
+    public static class MapMessage implements Serializable {
+        private static final long serialVersionUID = 1L;
+        public static final int MSG_BACKUP = 1;
+        public static final int MSG_RETRIEVE_BACKUP = 2;
+        public static final int MSG_PROXY = 3;
+        public static final int MSG_REMOVE = 4;
+        public static final int MSG_STATE = 5;
+        public static final int MSG_START = 6;
+        public static final int MSG_STOP = 7;
+        public static final int MSG_INIT = 8;
+        public static final int MSG_COPY = 9;
+        public static final int MSG_STATE_COPY = 10;
+
+        private byte[] mapId;
+        private int msgtype;
+        private boolean diff;
+        private transient Serializable key;
+        private transient Serializable value;
+        private byte[] valuedata;
+        private byte[] keydata;
+        private byte[] diffvalue;
+        private Member[] nodes;
+        private Member primary;
+        
+        @Override
+        public String toString() {
+            StringBuilder buf = new StringBuilder("MapMessage[context=");
+            buf.append(new String(mapId));
+            buf.append("; type=");
+            buf.append(getTypeDesc());
+            buf.append("; key=");
+            buf.append(key);
+            buf.append("; value=");
+            buf.append(value);
+            return buf.toString();
+        }
+        
+        public String getTypeDesc() {
+            switch (msgtype) {
+                case MSG_BACKUP: return "MSG_BACKUP";
+                case MSG_RETRIEVE_BACKUP: return "MSG_RETRIEVE_BACKUP";
+                case MSG_PROXY: return "MSG_PROXY";
+                case MSG_REMOVE: return "MSG_REMOVE";
+                case MSG_STATE: return "MSG_STATE";
+                case MSG_START: return "MSG_START";
+                case MSG_STOP: return "MSG_STOP";
+                case MSG_INIT: return "MSG_INIT";
+                case MSG_STATE_COPY: return "MSG_STATE_COPY";
+                case MSG_COPY: return "MSG_COPY";
+                default : return "UNKNOWN";
+            }
+        }
+
+        public MapMessage() {}
+
+        public MapMessage(byte[] mapId,int msgtype, boolean diff,
+                          Serializable key, Serializable value,
+                          byte[] diffvalue, Member primary, Member[] nodes)  {
+            this.mapId = mapId;
+            this.msgtype = msgtype;
+            this.diff = diff;
+            this.key = key;
+            this.value = value;
+            this.diffvalue = diffvalue;
+            this.nodes = nodes;
+            this.primary = primary;
+            setValue(value);
+            setKey(key);
+        }
+        
+        public void deserialize(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+            key(cls);
+            value(cls);
+        }
+
+        public int getMsgType() {
+            return msgtype;
+        }
+
+        public boolean isDiff() {
+            return diff;
+        }
+
+        public Serializable getKey() {
+            try {
+                return key(null);
+            } catch ( Exception x ) {
+                log.error("Deserialization error of the MapMessage.key",x);
+                return null;
+            }
+        }
+
+        public Serializable key(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+            if ( key!=null ) return key;
+            if ( keydata == null || keydata.length == 0 ) return null;
+            key = XByteBuffer.deserialize(keydata,0,keydata.length,cls);
+            keydata = null;
+            return key;
+        }
+        
+        public byte[] getKeyData() {
+            return keydata;
+        }
+        
+        public Serializable getValue() {
+            try {
+                return value(null);
+            } catch ( Exception x ) {
+                log.error("Deserialization error of the MapMessage.value",x);
+                return null;
+            }
+        }
+
+        public Serializable value(ClassLoader[] cls) throws IOException, ClassNotFoundException  {
+            if ( value!=null ) return value;
+            if ( valuedata == null || valuedata.length == 0 ) return null;
+            value = XByteBuffer.deserialize(valuedata,0,valuedata.length,cls);
+            valuedata = null;
+            return value;
+        }
+        
+        public byte[] getValueData() {
+            return valuedata;
+        }
+
+        public byte[] getDiffValue() {
+            return diffvalue;
+        }
+
+        public Member[] getBackupNodes() {
+            return nodes;
+        }
+
+        public Member getPrimary() {
+            return primary;
+        }
+        
+        private void setPrimary(Member m) {
+            primary = m;
+        }
+
+        public byte[] getMapId() {
+            return mapId;
+        }
+
+        public void setValue(Serializable value) {
+            try {
+                if ( value != null ) valuedata = XByteBuffer.serialize(value);
+                this.value = value;
+            }catch ( IOException x ) {
+                throw new RuntimeException(x);
+            }
+        }
+        
+        public void setKey(Serializable key) {
+            try {
+                if (key != null) keydata = XByteBuffer.serialize(key);
+                this.key = key;
+            } catch (IOException x) {
+                throw new RuntimeException(x);
+            }
+        }
+        
+        protected Member[] readMembers(ObjectInput in) throws IOException, ClassNotFoundException {
+            int nodecount = in.readInt();
+            Member[] members = new Member[nodecount];
+            for ( int i=0; i<members.length; i++ ) {
+                byte[] d = new byte[in.readInt()];
+                in.readFully(d);
+                if (d.length > 0) members[i] = MemberImpl.getMember(d);
+            }
+            return members;
+        }
+        
+        protected void writeMembers(ObjectOutput out,Member[] members) throws IOException {
+            if ( members == null ) members = new Member[0];
+            out.writeInt(members.length);
+            for (int i=0; i<members.length; i++ ) {
+                if ( members[i] != null ) {
+                    byte[] d = members[i] != null ? ( (MemberImpl)members[i]).getData(false) : new byte[0];
+                    out.writeInt(d.length);
+                    out.write(d);
+                }
+            }
+        }
+        
+        
+        /**
+         * shallow clone
+         * @return Object
+         */
+        @Override
+        public Object clone() {
+            MapMessage msg = new MapMessage(this.mapId, this.msgtype, this.diff, this.key, this.value, this.diffvalue, this.primary, this.nodes);
+            msg.keydata = this.keydata;
+            msg.valuedata = this.valuedata;
+            return msg;
+        }
+    } //MapMessage
+
+
+    public Channel getChannel() {
+        return channel;
+    }
+
+    public byte[] getMapContextName() {
+        return mapContextName;
+    }
+
+    public RpcChannel getRpcChannel() {
+        return rpcChannel;
+    }
+
+    public long getRpcTimeout() {
+        return rpcTimeout;
+    }
+
+    public Object getStateMutex() {
+        return stateMutex;
+    }
+
+    public boolean isStateTransferred() {
+        return stateTransferred;
+    }
+
+    public MapOwner getMapOwner() {
+        return mapOwner;
+    }
+
+    public ClassLoader[] getExternalLoaders() {
+        return externalLoaders;
+    }
+
+    public int getChannelSendOptions() {
+        return channelSendOptions;
+    }
+
+    public long getAccessTimeout() {
+        return accessTimeout;
+    }
+
+    public void setMapOwner(MapOwner mapOwner) {
+        this.mapOwner = mapOwner;
+    }
+
+    public void setExternalLoaders(ClassLoader[] externalLoaders) {
+        this.externalLoaders = externalLoaders;
+    }
+
+    public void setChannelSendOptions(int channelSendOptions) {
+        this.channelSendOptions = channelSendOptions;
+    }
+
+    public void setAccessTimeout(long accessTimeout) {
+        this.accessTimeout = accessTimeout;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
new file mode 100644
index 0000000..a5c8294
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * A smart implementation of a stateful replicated map. uses primary/secondary backup strategy. 
+ * One node is always the primary and one node is always the backup.
+ * This map is synchronized across a cluster, and only has one backup member.<br/>
+ * A perfect usage for this map would be a session map for a session manager in a clustered environment.<br/>
+ * The only way to modify this list is to use the <code>put, putAll, remove</code> methods.
+ * entrySet, entrySetFull, keySet, keySetFull, returns all non modifiable sets.<br><br>
+ * If objects (values) in the map change without invoking <code>put()</code> or <code>remove()</code>
+ * the data can be distributed using two different methods:<br>
+ * <code>replicate(boolean)</code> and <code>replicate(Object, boolean)</code><br>
+ * These two methods are very important two understand. The map can work with two set of value objects:<br>
+ * 1. Serializable - the entire object gets serialized each time it is replicated<br>
+ * 2. ReplicatedMapEntry - this interface allows for a isDirty() flag and to replicate diffs if desired.<br>
+ * Implementing the <code>ReplicatedMapEntry</code> interface allows you to decide what objects 
+ * get replicated and how much data gets replicated each time.<br>
+ * If you implement a smart AOP mechanism to detect changes in underlying objects, you can replicate
+ * only those changes by implementing the ReplicatedMapEntry interface, and return true when isDiffable()
+ * is invoked.<br><br>
+ * 
+ * This map implementation doesn't have a background thread running to replicate changes.
+ * If you do have changes without invoking put/remove then you need to invoke one of the following methods:
+ * <ul>
+ * <li><code>replicate(Object,boolean)</code> - replicates only the object that belongs to the key</li>
+ * <li><code>replicate(boolean)</code> - Scans the entire map for changes and replicates data</li>
+ *  </ul>
+ * the <code>boolean</code> value in the <code>replicate</code> method used to decide 
+ * whether to only replicate objects that implement the <code>ReplicatedMapEntry</code> interface
+ * or to replicate all objects. If an object doesn't implement the <code>ReplicatedMapEntry</code> interface
+ * each time the object gets replicated the entire object gets serialized, hence a call to <code>replicate(true)</code>
+ * will replicate all objects in this map that are using this node as primary.
+ * 
+ * <br><br><b>REMBER TO CALL <code>breakdown()</code> or <code>finalize()</code> when you are done with the map to 
+ * avoid memory leaks.<br><br>
+ * TODO implement periodic sync/transfer thread
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class LazyReplicatedMap extends AbstractReplicatedMap {
+    private static final long serialVersionUID = 1L;
+    private static final Log log = LogFactory.getLog(LazyReplicatedMap.class);
+
+
+//------------------------------------------------------------------------------    
+//              CONSTRUCTORS / DESTRUCTORS
+//------------------------------------------------------------------------------   
+    /**
+         * Creates a new map
+         * @param channel The channel to use for communication
+         * @param timeout long - timeout for RPC messags
+         * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+         * @param initialCapacity int - the size of this map, see HashMap
+         * @param loadFactor float - load factor, see HashMap
+         */
+        public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, float loadFactor, ClassLoader[] cls) {
+            super(owner,channel,timeout,mapContextName,initialCapacity,loadFactor, Channel.SEND_OPTIONS_DEFAULT,cls);
+        }
+
+        /**
+         * Creates a new map
+         * @param channel The channel to use for communication
+         * @param timeout long - timeout for RPC messags
+         * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+         * @param initialCapacity int - the size of this map, see HashMap
+         */
+        public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
+            super(owner, channel,timeout,mapContextName,initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
+        }
+
+        /**
+         * Creates a new map
+         * @param channel The channel to use for communication
+         * @param timeout long - timeout for RPC messags
+         * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+         */
+        public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
+            super(owner, channel,timeout,mapContextName, AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY,AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
+        }
+
+
+//------------------------------------------------------------------------------    
+//              METHODS TO OVERRIDE    
+//------------------------------------------------------------------------------
+    @Override
+    protected int getStateMessageType() {
+        return AbstractReplicatedMap.MapMessage.MSG_STATE;
+    }
+
+    /**
+     * publish info about a map pair (key/value) to other nodes in the cluster
+     * @param key Object
+     * @param value Object
+     * @return Member - the backup node
+     * @throws ChannelException
+     */
+    @Override
+    protected Member[] publishEntryInfo(Object key, Object value) throws ChannelException {
+        if  (! (key instanceof Serializable && value instanceof Serializable)  ) return new Member[0];
+        Member[] members = getMapMembers();
+        int firstIdx = getNextBackupIndex();
+        int nextIdx = firstIdx;
+        Member[] backup = new Member[0];
+        
+        //there are no backups
+        if ( members.length == 0 || firstIdx == -1 ) return backup;
+        
+        boolean success = false;
+        do {
+            //select a backup node
+            Member next = members[nextIdx];
+            
+            //increment for the next round of back up selection
+            nextIdx = nextIdx + 1;
+            if ( nextIdx >= members.length ) nextIdx = 0;
+            
+            if (next == null) {
+                continue;
+            }
+            MapMessage msg = null;
+            try {
+                backup = wrap(next);
+                //publish the backup data to one node
+                msg = new MapMessage(getMapContextName(), MapMessage.MSG_BACKUP, false,
+                                     (Serializable) key, (Serializable) value, null, channel.getLocalMember(false), backup);
+                if ( log.isTraceEnabled() ) 
+                    log.trace("Publishing backup data:"+msg+" to: "+next.getName());
+                UniqueId id = getChannel().send(backup, msg, getChannelSendOptions());
+                if ( log.isTraceEnabled() )
+                    log.trace("Data published:"+msg+" msg Id:"+id);
+                //we published out to a backup, mark the test success
+                success = true;
+            }catch ( ChannelException x ) {
+                log.error("Unable to replicate backup key:"+key+" to backup:"+next+". Reason:"+x.getMessage(),x);
+            }
+            try {
+                //publish the data out to all nodes
+                Member[] proxies = excludeFromSet(backup, getMapMembers());
+                if (success && proxies.length > 0 ) {
+                    msg = new MapMessage(getMapContextName(), MapMessage.MSG_PROXY, false,
+                                         (Serializable) key, null, null, channel.getLocalMember(false),backup);
+                    if ( log.isTraceEnabled() ) 
+                    log.trace("Publishing proxy data:"+msg+" to: "+Arrays.toNameString(proxies));
+                    getChannel().send(proxies, msg, getChannelSendOptions());
+                }
+            }catch  ( ChannelException x ) {
+                //log the error, but proceed, this should only happen if a node went down,
+                //and if the node went down, then it can't receive the message, the others
+                //should still get it.
+                log.error("Unable to replicate proxy key:"+key+" to backup:"+next+". Reason:"+x.getMessage(),x);
+            }
+        } while ( !success && (firstIdx!=nextIdx));
+        return backup;
+    }
+
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/ReplicatedMap.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/ReplicatedMap.java
new file mode 100644
index 0000000..7076c57
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/ReplicatedMap.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.Serializable;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.Member;
+
+/**
+ * All-to-all replication for a hash map implementation. Each node in the cluster will carry an identical 
+ * copy of the map.<br><br>
+ * This map implementation doesn't have a background thread running to replicate changes.
+ * If you do have changes without invoking put/remove then you need to invoke one of the following methods:
+ * <ul>
+ * <li><code>replicate(Object,boolean)</code> - replicates only the object that belongs to the key</li>
+ * <li><code>replicate(boolean)</code> - Scans the entire map for changes and replicates data</li>
+ *  </ul>
+ * the <code>boolean</code> value in the <code>replicate</code> method used to decide
+ * whether to only replicate objects that implement the <code>ReplicatedMapEntry</code> interface
+ * or to replicate all objects. If an object doesn't implement the <code>ReplicatedMapEntry</code> interface
+ * each time the object gets replicated the entire object gets serialized, hence a call to <code>replicate(true)</code>
+ * will replicate all objects in this map that are using this node as primary.
+ *
+ * <br><br><b>REMBER TO CALL <code>breakdown()</code> or <code>finalize()</code> when you are done with the map to
+ * avoid memory leaks.<br><br>
+ * TODO implement periodic sync/transfer thread
+ * @author Filip Hanik
+ * @version 1.0
+ * 
+ * TODO memberDisappeared, should do nothing except change map membership
+ *       by default it relocates the primary objects
+ */
+public class ReplicatedMap extends AbstractReplicatedMap {
+
+    private static final long serialVersionUID = 1L;
+
+    //--------------------------------------------------------------------------
+    //              CONSTRUCTORS / DESTRUCTORS
+    //--------------------------------------------------------------------------
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     * @param initialCapacity int - the size of this map, see HashMap
+     * @param loadFactor float - load factor, see HashMap
+     */
+    public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity,float loadFactor, ClassLoader[] cls) {
+        super(owner,channel, timeout, mapContextName, initialCapacity, loadFactor, Channel.SEND_OPTIONS_DEFAULT, cls);
+    }
+
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     * @param initialCapacity int - the size of this map, see HashMap
+     */
+    public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
+        super(owner,channel, timeout, mapContextName, initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
+    }
+
+    /**
+     * Creates a new map
+     * @param channel The channel to use for communication
+     * @param timeout long - timeout for RPC messags
+     * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+     */
+    public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
+        super(owner, channel, timeout, mapContextName,AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
+    }
+
+//------------------------------------------------------------------------------
+//              METHODS TO OVERRIDE
+//------------------------------------------------------------------------------
+    @Override
+    protected int getStateMessageType() {
+        return AbstractReplicatedMap.MapMessage.MSG_STATE_COPY;
+    }
+    
+    /**
+     * publish info about a map pair (key/value) to other nodes in the cluster
+     * @param key Object
+     * @param value Object
+     * @return Member - the backup node
+     * @throws ChannelException
+     */
+    @Override
+    protected Member[] publishEntryInfo(Object key, Object value) throws ChannelException {
+        if  (! (key instanceof Serializable && value instanceof Serializable)  ) return new Member[0];
+        //select a backup node
+        Member[] backup = getMapMembers();
+
+        if (backup == null || backup.length == 0) return null;
+
+        //publish the data out to all nodes
+        MapMessage msg = new MapMessage(getMapContextName(), MapMessage.MSG_COPY, false,
+                                        (Serializable) key, (Serializable) value, null,channel.getLocalMember(false), backup);
+
+        getChannel().send(getMapMembers(), msg, getChannelSendOptions());
+
+        return backup;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/ReplicatedMapEntry.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/ReplicatedMapEntry.java
new file mode 100644
index 0000000..b44c606
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/ReplicatedMapEntry.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * 
+ * For smarter replication, an object can implement this interface to replicate diffs<br>
+ * The replication logic will call the methods in the following order:<br>
+ * <code>
+ * 1. if ( entry.isDirty() ) <br>
+ *      try {
+ * 2.     entry.lock();<br>
+ * 3.     byte[] diff = entry.getDiff();<br>
+ * 4.     entry.reset();<br>
+ *      } finally {<br>
+ * 5.     entry.unlock();<br>
+ *      }<br>
+ *    }<br>
+ * </code>
+ * <br>
+ * <br>
+ * When the data is deserialized the logic is called in the following order<br>
+ * <code>
+ * 1. ReplicatedMapEntry entry = (ReplicatedMapEntry)objectIn.readObject();<br>
+ * 2. if ( isBackup(entry)||isPrimary(entry) ) entry.setOwner(owner); <br>
+ * </code>
+ * <br>
+ * 
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public interface ReplicatedMapEntry extends Serializable {
+    
+    /**
+     * Has the object changed since last replication
+     * and is not in a locked state
+     * @return boolean
+     */
+    public boolean isDirty();
+    
+    /**
+     * If this returns true, the map will extract the diff using getDiff()
+     * Otherwise it will serialize the entire object.
+     * @return boolean
+     */
+    public boolean isDiffable();
+    
+    /**
+     * Returns a diff and sets the dirty map to false
+     * @return byte[]
+     * @throws IOException
+     */
+    public byte[] getDiff() throws IOException;
+    
+    
+    /**
+     * Applies a diff to an existing object.
+     * @param diff byte[]
+     * @param offset int
+     * @param length int
+     * @throws IOException
+     */
+    public void applyDiff(byte[] diff, int offset, int length) throws IOException, ClassNotFoundException;
+    
+    /**
+     * Resets the current diff state and resets the dirty flag
+     */
+    public void resetDiff();
+    
+    /**
+     * Lock during serialization
+     */
+    public void lock();
+    
+    /**
+     * Unlock after serialization
+     */
+    public void unlock();
+    
+    /**
+     * This method is called after the object has been 
+     * created on a remote map. On this method,
+     * the object can initialize itself for any data that wasn't 
+     * 
+     * @param owner Object
+     */
+    public void setOwner(Object owner);
+    
+    /**
+     * For accuracy checking, a serialized attribute can contain a version number
+     * This number increases as modifications are made to the data.
+     * The replicated map can use this to ensure accuracy on a periodic basis
+     * @return long - the version number or -1 if the data is not versioned
+     */
+    public long getVersion();
+    
+    /**
+     * Forces a certain version to a replicated map entry<br>
+     * @param version long
+     */
+    public void setVersion(long version);
+
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/Streamable.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/Streamable.java
new file mode 100644
index 0000000..5607bb2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/tipis/Streamable.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.tipis;
+
+import java.io.IOException;
+
+/**
+ * Example usage:
+ * <code><pre>
+ * byte[] data = new byte[1024];
+ * Streamable st = ....;
+ * while ( !st.eof() ) {
+ * &nbsp;&nbsp;int length = st.read(data,0,data.length);
+ * &nbsp;&nbsp;String s = new String(data,0,length);
+ * &nbsp;&nbsp;System.out.println(s);
+ * }
+ * </pre></code>
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public interface Streamable {
+    
+    /**
+     * returns true if the stream has reached its end
+     * @return boolean
+     */
+    public boolean eof();
+   
+    /**
+     * write data into the byte array starting at offset, maximum bytes read are (data.length-offset)
+     * @param data byte[] - the array to read data into
+     * @param offset int - start position for writing data
+     * @return int - the number of bytes written into the data buffer
+     */
+    public int write(byte[] data, int offset, int length) throws IOException;
+    
+    /**
+     * read data into the byte array starting at offset
+     * @param data byte[] - the array to read data into
+     * @param offset int - start position for writing data
+     * @param length - the desired read length
+     * @return int - the number of bytes read from the data buffer
+     */
+    public int read(byte[] data, int offset, int length) throws IOException;
+
+   
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/FastQueue.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/FastQueue.java
new file mode 100644
index 0000000..a667544
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/FastQueue.java
@@ -0,0 +1,367 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio.util;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+
+
+
+/**
+ * A fast queue that remover thread lock the adder thread. <br/>Limit the queue
+ * length when you have strange producer thread problems.
+ * 
+ * FIXME add i18n support to log messages
+ * @author Rainer Jung
+ * @author Peter Rossbach
+ * @version $Id: FastQueue.java,v 1.1 2011/06/28 21:14:18 rherrmann Exp $
+ */
+public class FastQueue {
+
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(FastQueue.class);
+
+    /**
+     * This is the actual queue
+     */
+    private SingleRemoveSynchronizedAddLock lock = null;
+
+    /**
+     * First Object at queue (consumer message)
+     */
+    private LinkObject first = null;
+
+    /**
+     * Last object in queue (producer Object)
+     */
+    private LinkObject last = null;
+
+    /**
+     * Current Queue elements size
+     */
+    private int size = 0;
+
+    /**
+     * check lock to detect strange threadings things
+     */
+    private boolean checkLock = false;
+
+    
+    private boolean inAdd = false;
+
+    private boolean inRemove = false;
+
+    private boolean inMutex = false;
+
+    /**
+     * limit the queue length ( default is unlimited)
+     */
+    private int maxQueueLength = 0;
+
+    /**
+     * addWaitTimeout for producer
+     */
+    private long addWaitTimeout = 10000L;
+
+    
+    /**
+     * removeWaitTimeout for consumer
+     */
+    private long removeWaitTimeout = 30000L;
+
+    /**
+     * enabled the queue
+     */
+    private boolean enabled = true;
+
+    /**
+     *  max queue size
+     */
+    private int maxSize = 0;
+
+    /**
+     * Generate Queue SingleRemoveSynchronizedAddLock and set add and wait
+     * Timeouts
+     */
+    public FastQueue() {
+        lock = new SingleRemoveSynchronizedAddLock();
+        lock.setAddWaitTimeout(addWaitTimeout);
+        lock.setRemoveWaitTimeout(removeWaitTimeout);
+    }
+
+    /**
+     * get current add wait timeout
+     * 
+     * @return current wait timeout
+     */
+    public long getAddWaitTimeout() {
+        addWaitTimeout = lock.getAddWaitTimeout();
+        return addWaitTimeout;
+    }
+
+    /**
+     * Set add wait timeout (default 10000 msec)
+     * 
+     * @param timeout
+     */
+    public void setAddWaitTimeout(long timeout) {
+        addWaitTimeout = timeout;
+        lock.setAddWaitTimeout(addWaitTimeout);
+    }
+
+    /**
+     * get current remove wait timeout
+     * 
+     * @return The timeout
+     */
+    public long getRemoveWaitTimeout() {
+        removeWaitTimeout = lock.getRemoveWaitTimeout();
+        return removeWaitTimeout;
+    }
+
+    /**
+     * set remove wait timeout ( default 30000 msec)
+     * 
+     * @param timeout
+     */
+    public void setRemoveWaitTimeout(long timeout) {
+        removeWaitTimeout = timeout;
+        lock.setRemoveWaitTimeout(removeWaitTimeout);
+    }
+
+    public int getMaxQueueLength() {
+        return maxQueueLength;
+    }
+
+    public void setMaxQueueLength(int length) {
+        maxQueueLength = length;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enable) {
+        enabled = enable;
+        if (!enabled) {
+            lock.abortRemove();
+            last = first = null;
+        }
+    }
+
+    /**
+     * @return Returns the checkLock.
+     */
+    public boolean isCheckLock() {
+        return checkLock;
+    }
+
+    /**
+     * @param checkLock The checkLock to set.
+     */
+    public void setCheckLock(boolean checkLock) {
+        this.checkLock = checkLock;
+    }
+
+    
+    /**
+     * @return The max size
+     */
+    public int getMaxSize() {
+        return maxSize;
+    }
+
+    /**
+     * @param size
+     */
+    public void setMaxSize(int size) {
+        maxSize = size;
+    }
+
+    
+    /**
+     * unlock queue for next add 
+     */
+    public void unlockAdd() {
+        lock.unlockAdd(size > 0 ? true : false);
+    }
+
+    /**
+     * unlock queue for next remove 
+     */
+    public void unlockRemove() {
+        lock.unlockRemove();
+    }
+
+    /**
+     * start queuing
+     */
+    public void start() {
+        setEnabled(true);
+    }
+
+    /**
+     * start queuing
+     */
+    public void stop() {
+        setEnabled(false);
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public SingleRemoveSynchronizedAddLock getLock() {
+        return lock;
+    }
+
+    /**
+     * Add new data to the queue.
+     * 
+     * FIXME extract some method
+     */
+    public boolean add(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+        boolean ok = true;
+
+        if (!enabled) {
+            if (log.isInfoEnabled())
+                log.info("FastQueue.add: queue disabled, add aborted");
+            return false;
+        }
+
+        lock.lockAdd();
+        try {
+            if (log.isTraceEnabled()) {
+                log.trace("FastQueue.add: starting with size " + size);
+            }
+            if (checkLock) {
+                if (inAdd)
+                    log.warn("FastQueue.add: Detected other add");
+                inAdd = true;
+                if (inMutex)
+                    log.warn("FastQueue.add: Detected other mutex in add");
+                inMutex = true;
+            }
+
+            if ((maxQueueLength > 0) && (size >= maxQueueLength)) {
+                ok = false;
+                if (log.isTraceEnabled()) {
+                    log.trace("FastQueue.add: Could not add, since queue is full (" + size + ">=" + maxQueueLength + ")");
+                }
+            } else {
+                LinkObject element = new LinkObject(msg,destination, payload);
+                if (size == 0) {
+                    first = last = element;
+                    size = 1;
+                } else {
+                    if (last == null) {
+                        ok = false;
+                        log.error("FastQueue.add: Could not add, since last is null although size is "+ size + " (>0)");
+                    } else {
+                        last.append(element);
+                        last = element;
+                        size++;
+                    }
+                }
+            }
+
+            if (first == null) {
+                log.error("FastQueue.add: first is null, size is " + size + " at end of add");
+            }
+            if (last == null) {
+                log.error("FastQueue.add: last is null, size is " + size+ " at end of add");
+            }
+
+            if (checkLock) {
+                if (!inMutex) log.warn("FastQueue.add: Cancelled by other mutex in add");
+                inMutex = false;
+                if (!inAdd) log.warn("FastQueue.add: Cancelled by other add");
+                inAdd = false;
+            }
+            if (log.isTraceEnabled()) log.trace("FastQueue.add: add ending with size " + size);
+
+        } finally {
+            lock.unlockAdd(true);
+        }
+        return ok;
+    }
+
+    /**
+     * Remove the complete queued object list.
+     * FIXME extract some method
+     */
+    public LinkObject remove() {
+        LinkObject element;
+        boolean gotLock;
+
+        if (!enabled) {
+            if (log.isInfoEnabled())
+                log.info("FastQueue.remove: queue disabled, remove aborted");
+            return null;
+        }
+
+        gotLock = lock.lockRemove();
+        try {
+
+            if (!gotLock) {
+                if (enabled) {
+                    if (log.isInfoEnabled())
+                        log.info("FastQueue.remove: Remove aborted although queue enabled");
+                } else {
+                    if (log.isInfoEnabled())
+                        log.info("FastQueue.remove: queue disabled, remove aborted");
+                }
+                return null;
+            }
+
+            if (log.isTraceEnabled()) {
+                log.trace("FastQueue.remove: remove starting with size " + size);
+            }
+            if (checkLock) {
+                if (inRemove)
+                    log.warn("FastQueue.remove: Detected other remove");
+                inRemove = true;
+                if (inMutex)
+                    log.warn("FastQueue.remove: Detected other mutex in remove");
+                inMutex = true;
+            }
+
+            element = first;
+
+            first = last = null;
+            size = 0;
+
+            if (checkLock) {
+                if (!inMutex)
+                    log.warn("FastQueue.remove: Cancelled by other mutex in remove");
+                inMutex = false;
+                if (!inRemove)
+                    log.warn("FastQueue.remove: Cancelled by other remove");
+                inRemove = false;
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("FastQueue.remove: remove ending with size " + size);
+            }
+
+        } finally {
+            lock.unlockRemove();
+        }
+        return element;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/LinkObject.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/LinkObject.java
new file mode 100644
index 0000000..76119f7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/LinkObject.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio.util;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ErrorHandler;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+
+/**
+ * The class <b>LinkObject</b> implements an element
+ * for a linked list, consisting of a general
+ * data object and a pointer to the next element.
+ *
+ * @author Rainer Jung
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Id: LinkObject.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+
+ */
+
+public class LinkObject {
+
+    private ChannelMessage msg;
+    private LinkObject next;
+    private byte[] key ;
+    private Member[] destination;
+    private InterceptorPayload payload;
+
+    /**
+     * Construct a new element from the data object.
+     * Sets the pointer to null.
+     *
+     * @param msg the message
+     * @param destination TBA
+     * @param payload The data object.
+     */
+    public LinkObject(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+        this.msg = msg;
+        this.next = null;
+        this.key = msg.getUniqueId();
+        this.payload = payload;
+        this.destination = destination;
+    }
+
+    /**
+     * Set the next element.
+     * @param next The next element.
+     */
+    public void append(LinkObject next) {
+        this.next = next;
+    }
+
+    /**
+     * Get the next element.
+     * @return The next element.
+     */
+    public LinkObject next() {
+        return next;
+    }
+    
+    public void setNext(LinkObject next) {
+        this.next = next;
+    }
+
+    /**
+     * Get the data object from the element.
+     * @return The data object from the element.
+     */
+    public ChannelMessage data() {
+        return msg;
+    }
+
+    /**
+     * Get the unique message id
+     * @return the unique message id
+     */
+    public byte[] getKey() {
+        return key;
+    }
+
+    public ErrorHandler getHandler() {
+        return payload!=null?payload.getErrorHandler():null;
+    }
+
+    public InterceptorPayload getPayload() {
+        return payload;
+    }
+
+    public Member[] getDestination() {
+        return destination;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/SingleRemoveSynchronizedAddLock.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/SingleRemoveSynchronizedAddLock.java
new file mode 100644
index 0000000..b1dcbbc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/util/SingleRemoveSynchronizedAddLock.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.bio.util;
+
+/**
+ * The class <b>SingleRemoveSynchronizedAddLock</b> implement locking for
+ * accessing the queue by a single remove thread and multiple add threads.
+ *
+ * A thread is only allowed to be either the remove or
+ * an add thread.
+ *
+ * The lock can either be owned by the remove thread
+ * or by a single add thread.
+ *
+ * If the remove thread tries to get the lock,
+ * but the queue is empty, it will block (poll)
+ * until an add threads adds an entry to the queue and
+ * releases the lock.
+ * 
+ * If the remove thread and add threads compete for
+ * the lock and an add thread releases the lock, then
+ * the remove thread will get the lock first.
+ *
+ * The remove thread removes all entries in the queue
+ * at once and processes them without further
+ * polling the queue.
+ *
+ * The lock is not reentrant, in the sense, that all
+ * threads must release an owned lock before competing
+ * for the lock again!
+ *
+ * @author Rainer Jung
+ * @author Peter Rossbach
+ * @version 1.1
+ */
+ 
+public class SingleRemoveSynchronizedAddLock {
+    
+    public SingleRemoveSynchronizedAddLock() {
+        // NO-OP
+    }
+    
+    public SingleRemoveSynchronizedAddLock(boolean dataAvailable) {
+        this.dataAvailable=dataAvailable;
+    }
+    
+    /**
+     * Time in milliseconds after which threads
+     * waiting for an add lock are woken up.
+     * This is used as a safety measure in case
+     * thread notification via the unlock methods
+     * has a bug.
+     */
+    private long addWaitTimeout = 10000L;
+
+    /**
+     * Time in milliseconds after which threads
+     * waiting for a remove lock are woken up.
+     * This is used as a safety measure in case
+     * thread notification via the unlock methods
+     * has a bug.
+     */
+    private long removeWaitTimeout = 30000L;
+
+    /**
+     * The current remove thread.
+     * It is set to the remove thread polling for entries.
+     * It is reset to null when the remove thread
+     * releases the lock and proceeds processing
+     * the removed entries.
+     */
+    private Thread remover = null;
+
+    /**
+     * A flag indicating, if an add thread owns the lock.
+     */
+    private boolean addLocked = false;
+
+    /**
+     * A flag indicating, if the remove thread owns the lock.
+     */
+    private boolean removeLocked = false;
+
+    /**
+     * A flag indicating, if the remove thread is allowed
+     * to wait for the lock. The flag is set to false, when aborting.
+     */
+    private boolean removeEnabled = true;
+
+    /**
+     * A flag indicating, if the remover needs polling.
+     * It indicates, if the locked object has data available
+     * to be removed.
+     */
+    private boolean dataAvailable = false;
+
+    /**
+     * @return Value of addWaitTimeout
+     */
+    public synchronized long getAddWaitTimeout() {
+        return addWaitTimeout;
+    }
+
+    /**
+     * Set value of addWaitTimeout
+     */
+    public synchronized void setAddWaitTimeout(long timeout) {
+        addWaitTimeout = timeout;
+    }
+
+    /**
+     * @return Value of removeWaitTimeout
+     */
+    public synchronized long getRemoveWaitTimeout() {
+        return removeWaitTimeout;
+    }
+
+    /**
+     * Set value of removeWaitTimeout
+     */
+    public synchronized void setRemoveWaitTimeout(long timeout) {
+        removeWaitTimeout = timeout;
+    }
+
+    /**
+     * Check if the locked object has data available
+     * i.e. the remover can stop poling and get the lock.
+     * @return True iff the lock Object has data available.
+     */
+    public synchronized boolean isDataAvailable() {
+        return dataAvailable;
+    }
+
+    /**
+     * Check if an add thread owns the lock.
+     * @return True iff an add thread owns the lock.
+     */
+    public synchronized boolean isAddLocked() {
+        return addLocked;
+    }
+
+    /**
+     * Check if the remove thread owns the lock.
+     * @return True iff the remove thread owns the lock.
+     */
+    public synchronized boolean isRemoveLocked() {
+        return removeLocked;
+    }
+
+    /**
+     * Check if the remove thread is polling.
+     * @return True iff the remove thread is polling.
+     */
+    public synchronized boolean isRemovePolling() {
+        if ( remover != null ) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Acquires the lock by an add thread and sets the add flag.
+     * If any add thread or the remove thread already acquired the lock
+     * this add thread will block until the lock is released.
+     */
+    public synchronized void lockAdd() {
+        if ( addLocked || removeLocked ) {
+            do {
+                try {
+                    wait(addWaitTimeout);
+                } catch ( InterruptedException e ) {
+                    Thread.interrupted();
+                }
+            } while ( addLocked || removeLocked );
+        }
+        addLocked=true;
+    }
+
+    /**
+     * Acquires the lock by the remove thread and sets the remove flag.
+     * If any add thread already acquired the lock or the queue is
+     * empty, the remove thread will block until the lock is released
+     * and the queue is not empty.
+     */
+    public synchronized boolean lockRemove() {
+        removeLocked=false;
+        removeEnabled=true;
+        if ( ( addLocked || ! dataAvailable ) && removeEnabled ) {
+            remover=Thread.currentThread();
+            do {
+                try {
+                    wait(removeWaitTimeout);
+                } catch ( InterruptedException e ) {
+                    Thread.interrupted();
+                }
+            } while ( ( addLocked || ! dataAvailable ) && removeEnabled );
+            remover=null;
+        }
+        if ( removeEnabled ) {
+            removeLocked=true;
+        } 
+        return removeLocked;
+    }
+
+    /**
+     * Releases the lock by an add thread and reset the remove flag.
+     * If the reader thread is polling, notify it.
+     */
+    public synchronized void unlockAdd(boolean dataAvailable) {
+        addLocked=false;
+        this.dataAvailable=dataAvailable;
+        if ( ( remover != null ) && ( dataAvailable || ! removeEnabled ) ) {
+            remover.interrupt();
+        } else {
+            notifyAll();
+        }
+    }
+
+    /**
+     * Releases the lock by the remove thread and reset the add flag.
+     * Notify all waiting add threads,
+     * that the lock has been released by the remove thread.
+     */
+    public synchronized void unlockRemove() {
+        removeLocked=false;
+        dataAvailable=false;
+        notifyAll();
+    }
+
+    /**
+     * Abort any polling remover thread
+     */
+    public synchronized void abortRemove() {
+        removeEnabled=false;
+        if ( remover != null ) {
+            remover.interrupt();
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioReceiver.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioReceiver.java
new file mode 100644
index 0000000..9c6deaa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioReceiver.java
@@ -0,0 +1,440 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.nio;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.ClosedSelectorException;
+import java.nio.channels.DatagramChannel;
+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.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+
+import org.apache.catalina.tribes.io.ObjectReader;
+import org.apache.catalina.tribes.transport.AbstractRxTask;
+import org.apache.catalina.tribes.transport.Constants;
+import org.apache.catalina.tribes.transport.ReceiverBase;
+import org.apache.catalina.tribes.transport.RxTaskPool;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * @author Filip Hanik
+ * @version $Id: NioReceiver.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public class NioReceiver extends ReceiverBase implements Runnable {
+
+    private static final Log log = LogFactory.getLog(NioReceiver.class);
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "NioReceiver/1.0";
+
+    private Selector selector = null;
+    private ServerSocketChannel serverChannel = null;
+    private DatagramChannel datagramChannel = null;
+
+    protected LinkedList<Runnable> events = new LinkedList<Runnable>();
+//    private Object interestOpsMutex = new Object();
+
+    public NioReceiver() {
+    }
+
+    /**
+     * Return descriptive information about this implementation and the
+     * corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo() {
+        return (info);
+    }
+
+//    public Object getInterestOpsMutex() {
+//        return interestOpsMutex;
+//    }
+
+    @Override
+    public void stop() {
+        this.stopListening();
+        super.stop();
+    }
+
+    /**
+     * start cluster receiver
+     * @throws IOException
+     * @see org.apache.catalina.tribes.ChannelReceiver#start()
+     */
+    @Override
+    public void start() throws IOException {
+        super.start();
+        try {
+            setPool(new RxTaskPool(getMaxThreads(),getMinThreads(),this));
+        } catch (Exception x) {
+            log.fatal("ThreadPool can initilzed. Listener not started", x);
+            if ( x instanceof IOException ) throw (IOException)x;
+            else throw new IOException(x.getMessage());
+        }
+        try {
+            getBind();
+            bind();
+            Thread t = new Thread(this, "NioReceiver");
+            t.setDaemon(true);
+            t.start();
+        } catch (Exception x) {
+            log.fatal("Unable to start cluster receiver", x);
+            if ( x instanceof IOException ) throw (IOException)x;
+            else throw new IOException(x.getMessage());
+        }
+    }
+
+    public AbstractRxTask createRxTask() {
+        NioReplicationTask thread = new NioReplicationTask(this,this);
+        thread.setUseBufferPool(this.getUseBufferPool());
+        thread.setRxBufSize(getRxBufSize());
+        thread.setOptions(getWorkerThreadOptions());
+        return thread;
+    }
+
+
+
+    protected void bind() throws IOException {
+        // allocate an unbound server socket channel
+        serverChannel = ServerSocketChannel.open();
+        // Get the associated ServerSocket to bind it with
+        ServerSocket serverSocket = serverChannel.socket();
+        // create a new Selector for use below
+        selector = Selector.open();
+        // set the port the server channel will listen to
+        //serverSocket.bind(new InetSocketAddress(getBind(), getTcpListenPort()));
+        bind(serverSocket,getPort(),getAutoBind());
+        // set non-blocking mode for the listening socket
+        serverChannel.configureBlocking(false);
+        // register the ServerSocketChannel with the Selector
+        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
+
+        //set up the datagram channel
+        if (this.getUdpPort()>0) {
+            datagramChannel = DatagramChannel.open();
+            datagramChannel.configureBlocking(false);
+            //bind to the address to avoid security checks
+            bindUdp(datagramChannel.socket(),getUdpPort(),getAutoBind());
+        }
+
+
+
+    }
+
+    public void addEvent(Runnable event) {
+        if ( selector != null ) {
+            synchronized (events) {
+                events.add(event);
+            }
+            if ( log.isTraceEnabled() ) log.trace("Adding event to selector:"+event);
+            if ( isListening() && selector!=null ) selector.wakeup();
+        }
+    }
+
+    public void events() {
+        if ( events.size() == 0 ) return;
+        synchronized (events) {
+            Runnable r = null;
+            while ( (events.size() > 0) && (r = events.removeFirst()) != null ) {
+                try {
+                    if ( log.isTraceEnabled() ) log.trace("Processing event in selector:"+r);
+                    r.run();
+                } catch ( Exception x ) {
+                    log.error("",x);
+                }
+            }
+            events.clear();
+        }
+    }
+
+    public static void cancelledKey(SelectionKey key) {
+        ObjectReader reader = (ObjectReader)key.attachment();
+        if ( reader != null ) {
+            reader.setCancelled(true);
+            reader.finish();
+        }
+        key.cancel();
+        key.attach(null);
+        if (key.channel() instanceof SocketChannel)
+            try { ((SocketChannel)key.channel()).socket().close(); } catch (IOException e) { if (log.isDebugEnabled()) log.debug("", e); }
+        if (key.channel() instanceof DatagramChannel)
+            try { ((DatagramChannel)key.channel()).socket().close(); } catch (Exception e) { if (log.isDebugEnabled()) log.debug("", e); }
+        try { key.channel().close(); } catch (IOException e) { if (log.isDebugEnabled()) log.debug("", e); }
+
+    }
+    protected long lastCheck = System.currentTimeMillis();
+    protected void socketTimeouts() {
+        long now = System.currentTimeMillis();
+        if ( (now-lastCheck) < getSelectorTimeout() ) return;
+        //timeout
+        Selector tmpsel = selector;
+        Set<SelectionKey> keys =  (isListening()&&tmpsel!=null)?tmpsel.keys():null;
+        if ( keys == null ) return;
+        for (Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext();) {
+            SelectionKey key = iter.next();
+            try {
+//                if (key.interestOps() == SelectionKey.OP_READ) {
+//                    //only timeout sockets that we are waiting for a read from
+//                    ObjectReader ka = (ObjectReader) key.attachment();
+//                    long delta = now - ka.getLastAccess();
+//                    if (delta > (long) getTimeout()) {
+//                        cancelledKey(key);
+//                    }
+//                }
+//                else
+                if ( key.interestOps() == 0 ) {
+                    //check for keys that didn't make it in.
+                    ObjectReader ka = (ObjectReader) key.attachment();
+                    if ( ka != null ) {
+                        long delta = now - ka.getLastAccess();
+                        if (delta > getTimeout() && (!ka.isAccessed())) {
+                            if (log.isWarnEnabled())
+                                log.warn("Channel key is registered, but has had no interest ops for the last "+getTimeout()+" ms. (cancelled:"+ka.isCancelled()+"):"+key+" last access:"+new java.sql.Timestamp(ka.getLastAccess())+" Possible cause: all threads used, perform thread dump");
+                            ka.setLastAccess(now);
+                            //key.interestOps(SelectionKey.OP_READ);
+                        }//end if
+                    } else {
+                        cancelledKey(key);
+                    }//end if
+                }//end if
+            }catch ( CancelledKeyException ckx ) {
+                cancelledKey(key);
+            }
+        }
+        lastCheck = System.currentTimeMillis();
+    }
+
+
+    /**
+     * get data from channel and store in byte array
+     * send it to cluster
+     * @throws IOException
+     * @throws java.nio.channels.ClosedChannelException
+     */
+    protected void listen() throws Exception {
+        if (doListen()) {
+            log.warn("ServerSocketChannel already started");
+            return;
+        }
+
+        setListen(true);
+        if (selector!=null && datagramChannel!=null) {
+            ObjectReader oreader = new ObjectReader(MAX_UDP_SIZE); //max size for a datagram packet
+            datagramChannel.socket().setSendBufferSize(getUdpTxBufSize());
+            datagramChannel.socket().setReceiveBufferSize(getUdpRxBufSize());
+            datagramChannel.socket().setReuseAddress(getSoReuseAddress());
+            datagramChannel.socket().setSoTimeout(getTimeout());
+            datagramChannel.socket().setTrafficClass(getSoTrafficClass());
+            registerChannel(selector,datagramChannel,SelectionKey.OP_READ,oreader);
+        }
+
+        while (doListen() && selector != null) {
+            // this may block for a long time, upon return the
+            // selected set contains keys of the ready channels
+            try {
+                events();
+                socketTimeouts();
+                int n = selector.select(getSelectorTimeout());
+                if (n == 0) {
+                    //there is a good chance that we got here
+                    //because the TcpReplicationThread called
+                    //selector wakeup().
+                    //if that happens, we must ensure that that
+                    //thread has enough time to call interestOps
+//                    synchronized (interestOpsMutex) {
+                        //if we got the lock, means there are no
+                        //keys trying to register for the
+                        //interestOps method
+//                    }
+                    continue; // nothing to do
+                }
+                // get an iterator over the set of selected keys
+                Iterator<SelectionKey> it = (selector!=null)?selector.selectedKeys().iterator():null;
+                // look at each key in the selected set
+                while (selector!=null && it.hasNext()) {
+                    SelectionKey key = it.next();
+                    // Is a new connection coming in?
+                    if (key.isAcceptable()) {
+                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
+                        SocketChannel channel = server.accept();
+                        channel.socket().setReceiveBufferSize(getRxBufSize());
+                        channel.socket().setSendBufferSize(getTxBufSize());
+                        channel.socket().setTcpNoDelay(getTcpNoDelay());
+                        channel.socket().setKeepAlive(getSoKeepAlive());
+                        channel.socket().setOOBInline(getOoBInline());
+                        channel.socket().setReuseAddress(getSoReuseAddress());
+                        channel.socket().setSoLinger(getSoLingerOn(),getSoLingerTime());
+                        channel.socket().setTrafficClass(getSoTrafficClass());
+                        channel.socket().setSoTimeout(getTimeout());
+                        Object attach = new ObjectReader(channel);
+                        registerChannel(selector,
+                                        channel,
+                                        SelectionKey.OP_READ,
+                                        attach);
+                    }
+                    // is there data to read on this channel?
+                    if (key.isReadable()) {
+                        readDataFromSocket(key);
+                    } else {
+                        key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
+                    }
+
+                    // remove key from selected set, it's been handled
+                    if (selector!=null) it.remove();
+                }
+            } catch (java.nio.channels.ClosedSelectorException cse) {
+                // ignore is normal at shutdown or stop listen socket
+            } catch (java.nio.channels.CancelledKeyException nx) {
+                log.warn("Replication client disconnected, error when polling key. Ignoring client.");
+            } catch (Throwable t) {
+                if (t instanceof ThreadDeath) {
+                    throw (ThreadDeath) t;
+                }
+                if (t instanceof VirtualMachineError) {
+                    throw (VirtualMachineError) t;
+                }
+                log.error("Unable to process request in NioReceiver", t);
+            }
+
+        }
+        serverChannel.close();
+        if (datagramChannel!=null) {
+            try {
+                datagramChannel.close();
+            }catch (Exception iox) {
+                if (log.isDebugEnabled()) log.debug("Unable to close datagram channel.",iox);
+            }
+            datagramChannel=null;
+        }
+        closeSelector();
+    }
+
+
+
+    /**
+     * Close Selector.
+     *
+     * @see org.apache.catalina.tribes.transport.ReceiverBase#stop()
+     */
+    protected void stopListening() {
+        setListen(false);
+        if (selector != null) {
+            try {
+                selector.wakeup();
+                closeSelector();
+            } catch (Exception x) {
+                log.error("Unable to close cluster receiver selector.", x);
+            } finally {
+                selector = null;
+            }
+        }
+    }
+
+    private void closeSelector() throws IOException {
+        Selector selector = this.selector;
+        this.selector = null;
+        if (selector==null) return;
+        try {
+            Iterator<SelectionKey> it = selector.keys().iterator();
+            // look at each key in the selected set
+            while (it.hasNext()) {
+                SelectionKey key = it.next();
+                key.channel().close();
+                key.attach(null);
+                key.cancel();
+            }
+        }catch ( IOException ignore ){
+            if (log.isWarnEnabled()) {
+                log.warn("Unable to cleanup on selector close.",ignore);
+            }
+        }catch ( ClosedSelectorException ignore){}
+        selector.close();
+    }
+
+    // ----------------------------------------------------------
+
+    /**
+     * Register the given channel with the given selector for
+     * the given operations of interest
+     */
+    protected void registerChannel(Selector selector,
+                                   SelectableChannel channel,
+                                   int ops,
+                                   Object attach) throws Exception {
+        if (channel == null)return; // could happen
+        // set the new channel non-blocking
+        channel.configureBlocking(false);
+        // register it with the selector
+        channel.register(selector, ops, attach);
+    }
+
+    /**
+     * Start thread and listen
+     */
+    public void run() {
+        try {
+            listen();
+        } catch (Exception x) {
+            log.error("Unable to run replication listener.", x);
+        }
+    }
+
+    // ----------------------------------------------------------
+
+    /**
+     * Sample data handler method for a channel with data ready to read.
+     * @param key A SelectionKey object associated with a channel
+     *  determined by the selector to be ready for reading.  If the
+     *  channel returns an EOF condition, it is closed here, which
+     *  automatically invalidates the associated key.  The selector
+     *  will then de-register the channel on the next select call.
+     */
+    protected void readDataFromSocket(SelectionKey key) throws Exception {
+        NioReplicationTask task = (NioReplicationTask) getTaskPool().getRxTask();
+        if (task == null) {
+            // No threads/tasks available, do nothing, the selection
+            // loop will keep calling this method until a
+            // thread becomes available, the thread pool itself has a waiting mechanism
+            // so we will not wait here.
+            if (log.isDebugEnabled()) log.debug("No TcpReplicationThread available");
+        } else {
+            // invoking this wakes up the worker thread then returns
+            //add task to thread pool
+            task.serviceChannel(key);
+            getExecutor().execute(task);
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java
new file mode 100644
index 0000000..86aa981
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.nio;
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.RemoteProcessException;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.io.BufferPool;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.ListenCallback;
+import org.apache.catalina.tribes.io.ObjectReader;
+import org.apache.catalina.tribes.transport.AbstractRxTask;
+import org.apache.catalina.tribes.transport.Constants;
+import org.apache.catalina.tribes.util.Logs;
+
+/**
+ * A worker thread class which can drain channels and echo-back the input. Each
+ * instance is constructed with a reference to the owning thread pool object.
+ * When started, the thread loops forever waiting to be awakened to service the
+ * channel associated with a SelectionKey object. The worker is tasked by
+ * calling its serviceChannel() method with a SelectionKey object. The
+ * serviceChannel() method stores the key reference in the thread object then
+ * calls notify() to wake it up. When the channel has been drained, the worker
+ * thread returns itself to its parent pool.
+ *
+ * @author Filip Hanik
+ *
+ * @version $Id: NioReplicationTask.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public class NioReplicationTask extends AbstractRxTask {
+
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( NioReplicationTask.class );
+
+    private ByteBuffer buffer = null;
+    private SelectionKey key;
+    private int rxBufSize;
+    private NioReceiver receiver;
+    public NioReplicationTask (ListenCallback callback, NioReceiver receiver)
+    {
+        super(callback);
+        this.receiver = receiver;
+    }
+
+    // loop forever waiting for work to do
+    public synchronized void run() {
+        if ( buffer == null ) {
+            int size = getRxBufSize();
+            if (key.channel() instanceof DatagramChannel) {
+                size = ChannelReceiver.MAX_UDP_SIZE;
+            }
+            if ( (getOptions() & OPTION_DIRECT_BUFFER) == OPTION_DIRECT_BUFFER) {
+                buffer = ByteBuffer.allocateDirect(size);
+            } else {
+                buffer = ByteBuffer.allocate(size);
+            }
+        } else {
+            buffer.clear();
+        }
+        if (key == null) {
+            return; // just in case
+        }
+        if ( log.isTraceEnabled() )
+            log.trace("Servicing key:"+key);
+
+        try {
+            ObjectReader reader = (ObjectReader)key.attachment();
+            if ( reader == null ) {
+                if ( log.isTraceEnabled() )
+                    log.trace("No object reader, cancelling:"+key);
+                cancelKey(key);
+            } else {
+                if ( log.isTraceEnabled() )
+                    log.trace("Draining channel:"+key);
+
+                drainChannel(key, reader);
+            }
+        } catch (Exception e) {
+            //this is common, since the sockets on the other
+            //end expire after a certain time.
+            if ( e instanceof CancelledKeyException ) {
+                //do nothing
+            } else if ( e instanceof IOException ) {
+                //dont spew out stack traces for IO exceptions unless debug is enabled.
+                if (log.isDebugEnabled()) log.debug ("IOException in replication worker, unable to drain channel. Probable cause: Keep alive socket closed["+e.getMessage()+"].", e);
+                else log.warn ("IOException in replication worker, unable to drain channel. Probable cause: Keep alive socket closed["+e.getMessage()+"].");
+            } else if ( log.isErrorEnabled() ) {
+                //this is a real error, log it.
+                log.error("Exception caught in TcpReplicationThread.drainChannel.",e);
+            }
+            cancelKey(key);
+        } finally {
+
+        }
+        key = null;
+        // done, ready for more, return to pool
+        getTaskPool().returnWorker (this);
+    }
+
+    /**
+     * Called to initiate a unit of work by this worker thread
+     * on the provided SelectionKey object.  This method is
+     * synchronized, as is the run() method, so only one key
+     * can be serviced at a given time.
+     * Before waking the worker thread, and before returning
+     * to the main selection loop, this key's interest set is
+     * updated to remove OP_READ.  This will cause the selector
+     * to ignore read-readiness for this channel while the
+     * worker thread is servicing it.
+     */
+    public synchronized void serviceChannel (SelectionKey key) {
+        if ( log.isTraceEnabled() ) log.trace("About to service key:"+key);
+        ObjectReader reader = (ObjectReader)key.attachment();
+        if ( reader != null ) reader.setLastAccess(System.currentTimeMillis());
+        this.key = key;
+        key.interestOps (key.interestOps() & (~SelectionKey.OP_READ));
+        key.interestOps (key.interestOps() & (~SelectionKey.OP_WRITE));
+    }
+
+    /**
+     * The actual code which drains the channel associated with
+     * the given key.  This method assumes the key has been
+     * modified prior to invocation to turn off selection
+     * interest in OP_READ.  When this method completes it
+     * re-enables OP_READ and calls wakeup() on the selector
+     * so the selector will resume watching this channel.
+     */
+    protected void drainChannel (final SelectionKey key, ObjectReader reader) throws Exception {
+        reader.setLastAccess(System.currentTimeMillis());
+        reader.access();
+        ReadableByteChannel channel = (ReadableByteChannel) key.channel();
+        int count=-1;
+        buffer.clear();         // make buffer empty
+        SocketAddress saddr = null;
+
+        if (channel instanceof SocketChannel) {
+            // loop while data available, channel is non-blocking
+            while ((count = channel.read (buffer)) > 0) {
+                buffer.flip();      // make buffer readable
+                if ( buffer.hasArray() )
+                    reader.append(buffer.array(),0,count,false);
+                else
+                    reader.append(buffer,count,false);
+                buffer.clear();     // make buffer empty
+                //do we have at least one package?
+                if ( reader.hasPackage() ) break;
+            }
+        } else if (channel instanceof DatagramChannel) {
+            DatagramChannel dchannel = (DatagramChannel)channel;
+            saddr = dchannel.receive(buffer);
+            buffer.flip();      // make buffer readable
+            if ( buffer.hasArray() )
+                reader.append(buffer.array(),0,buffer.limit()-buffer.position(),false);
+            else
+                reader.append(buffer,buffer.limit()-buffer.position(),false);
+            buffer.clear();     // make buffer empty
+            //did we get a package
+            count = reader.hasPackage()?1:-1;
+        }
+
+        int pkgcnt = reader.count();
+
+        if (count < 0 && pkgcnt == 0 ) {
+            //end of stream, and no more packages to process
+            remoteEof(key);
+            return;
+        }
+
+        ChannelMessage[] msgs = pkgcnt == 0? ChannelData.EMPTY_DATA_ARRAY : reader.execute();
+
+        registerForRead(key,reader);//register to read new data, before we send it off to avoid dead locks
+
+        for ( int i=0; i<msgs.length; i++ ) {
+            /**
+             * Use send ack here if you want to ack the request to the remote
+             * server before completing the request
+             * This is considered an asynchronized request
+             */
+            if (ChannelData.sendAckAsync(msgs[i].getOptions())) sendAck(key,(WritableByteChannel)channel,Constants.ACK_COMMAND,saddr);
+            try {
+                if ( Logs.MESSAGES.isTraceEnabled() ) {
+                    try {
+                        Logs.MESSAGES.trace("NioReplicationThread - Received msg:" + new UniqueId(msgs[i].getUniqueId()) + " at " + new java.sql.Timestamp(System.currentTimeMillis()));
+                    }catch ( Throwable t ) {}
+                }
+                //process the message
+                getCallback().messageDataReceived(msgs[i]);
+                /**
+                 * Use send ack here if you want the request to complete on this
+                 * server before sending the ack to the remote server
+                 * This is considered a synchronized request
+                 */
+                if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,(WritableByteChannel)channel,Constants.ACK_COMMAND,saddr);
+            }catch ( RemoteProcessException e ) {
+                if ( log.isDebugEnabled() ) log.error("Processing of cluster message failed.",e);
+                if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,(WritableByteChannel)channel,Constants.FAIL_ACK_COMMAND,saddr);
+            }catch ( Exception e ) {
+                log.error("Processing of cluster message failed.",e);
+                if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,(WritableByteChannel)channel,Constants.FAIL_ACK_COMMAND,saddr);
+            }
+            if ( getUseBufferPool() ) {
+                BufferPool.getBufferPool().returnBuffer(msgs[i].getMessage());
+                msgs[i].setMessage(null);
+            }
+        }
+
+        if (count < 0) {
+            remoteEof(key);
+            return;
+        }
+    }
+
+    private void remoteEof(SelectionKey key) {
+        // close channel on EOF, invalidates the key
+        if ( log.isDebugEnabled() ) log.debug("Channel closed on the remote end, disconnecting");
+        cancelKey(key);
+    }
+
+    protected void registerForRead(final SelectionKey key, ObjectReader reader) {
+        if ( log.isTraceEnabled() )
+            log.trace("Adding key for read event:"+key);
+        reader.finish();
+        //register our OP_READ interest
+        Runnable r = new Runnable() {
+            public void run() {
+                try {
+                    if (key.isValid()) {
+                        // cycle the selector so this key is active again
+                        key.selector().wakeup();
+                        // resume interest in OP_READ, OP_WRITE
+                        int resumeOps = key.interestOps() | SelectionKey.OP_READ;
+                        key.interestOps(resumeOps);
+                        if ( log.isTraceEnabled() )
+                            log.trace("Registering key for read:"+key);
+                    }
+                } catch (CancelledKeyException ckx ) {
+                    NioReceiver.cancelledKey(key);
+                    if ( log.isTraceEnabled() )
+                        log.trace("CKX Cancelling key:"+key);
+
+                } catch (Exception x) {
+                    log.error("Error registering key for read:"+key,x);
+                }
+            }
+        };
+        receiver.addEvent(r);
+    }
+
+    private void cancelKey(final SelectionKey key) {
+        if ( log.isTraceEnabled() )
+            log.trace("Adding key for cancel event:"+key);
+
+        ObjectReader reader = (ObjectReader)key.attachment();
+        if ( reader != null ) {
+            reader.setCancelled(true);
+            reader.finish();
+        }
+        Runnable cx = new Runnable() {
+            public void run() {
+                if ( log.isTraceEnabled() )
+                    log.trace("Cancelling key:"+key);
+
+                NioReceiver.cancelledKey(key);
+            }
+        };
+        receiver.addEvent(cx);
+    }
+
+
+    /**
+     * send a reply-acknowledgement (6,2,3), sends it doing a busy write, the ACK is so small
+     * that it should always go to the buffer
+     * @param key
+     * @param channel
+     */
+    protected void sendAck(SelectionKey key, WritableByteChannel channel, byte[] command, SocketAddress udpaddr) {
+        try {
+
+            ByteBuffer buf = ByteBuffer.wrap(command);
+            int total = 0;
+            if (channel instanceof DatagramChannel) {
+                DatagramChannel dchannel = (DatagramChannel)channel;
+                //were using a shared channel, document says its thread safe
+                //TODO check optimization, one channel per thread?
+                while ( total < command.length ) {
+                    total += dchannel.send(buf, udpaddr);
+                }
+            } else {
+                while ( total < command.length ) {
+                    total += channel.write(buf);
+                }
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("ACK sent to " +
+                        ( (channel instanceof SocketChannel) ?
+                          ((SocketChannel)channel).socket().getInetAddress() :
+                          ((DatagramChannel)channel).socket().getInetAddress()));
+            }
+        } catch ( java.io.IOException x ) {
+            log.warn("Unable to send ACK back through channel, channel disconnected?: "+x.getMessage());
+        }
+    }
+
+    public void setRxBufSize(int rxBufSize) {
+        this.rxBufSize = rxBufSize;
+    }
+
+    public int getRxBufSize() {
+        return rxBufSize;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioSender.java
new file mode 100644
index 0000000..8cedb54
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/NioSender.java
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport.nio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+
+import org.apache.catalina.tribes.RemoteProcessException;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * This class is NOT thread safe and should never be used with more than one thread at a time
+ *
+ * This is a state machine, handled by the process method
+ * States are:
+ * - NOT_CONNECTED -> connect() -> CONNECTED
+ * - CONNECTED -> setMessage() -> READY TO WRITE
+ * - READY_TO_WRITE -> write() -> READY TO WRITE | READY TO READ
+ * - READY_TO_READ -> read() -> READY_TO_READ | TRANSFER_COMPLETE
+ * - TRANSFER_COMPLETE -> CONNECTED
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class NioSender extends AbstractSender {
+
+    private static final Log log = LogFactory.getLog(NioSender.class);
+
+
+
+    protected Selector selector;
+    protected SocketChannel socketChannel = null;
+    protected DatagramChannel dataChannel = null;
+
+    /*
+     * STATE VARIABLES *
+     */
+    protected ByteBuffer readbuf = null;
+    protected ByteBuffer writebuf = null;
+    protected byte[] current = null;
+    protected XByteBuffer ackbuf = new XByteBuffer(128,true);
+    protected int remaining = 0;
+    protected boolean complete;
+
+    protected boolean connecting = false;
+
+    public NioSender() {
+        super();
+
+    }
+
+    /**
+     * State machine to send data
+     * @param key SelectionKey
+     * @return boolean
+     * @throws IOException
+     */
+    public boolean process(SelectionKey key, boolean waitForAck) throws IOException {
+        int ops = key.readyOps();
+        key.interestOps(key.interestOps() & ~ops);
+        //in case disconnect has been called
+        if ((!isConnected()) && (!connecting)) throw new IOException("Sender has been disconnected, can't selection key.");
+        if ( !key.isValid() ) throw new IOException("Key is not valid, it must have been cancelled.");
+        if ( key.isConnectable() ) {
+            if ( socketChannel.finishConnect() ) {
+                completeConnect();
+                if ( current != null ) key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
+                return false;
+            } else  {
+                //wait for the connection to finish
+                key.interestOps(key.interestOps() | SelectionKey.OP_CONNECT);
+                return false;
+            }//end if
+        } else if ( key.isWritable() ) {
+            boolean writecomplete = write(key);
+            if ( writecomplete ) {
+                //we are completed, should we read an ack?
+                if ( waitForAck ) {
+                    //register to read the ack
+                    key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+                } else {
+                    //if not, we are ready, setMessage will reregister us for another write interest
+                    //do a health check, we have no way of verify a disconnected
+                    //socket since we don't register for OP_READ on waitForAck=false
+                    read(key);//this causes overhead
+                    setRequestCount(getRequestCount()+1);
+                    return true;
+                }
+            } else {
+                //we are not complete, lets write some more
+                key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
+            }//end if
+        } else if ( key.isReadable() ) {
+            boolean readcomplete = read(key);
+            if ( readcomplete ) {
+                setRequestCount(getRequestCount()+1);
+                return true;
+            } else {
+                key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+            }//end if
+        } else {
+            //unknown state, should never happen
+            log.warn("Data is in unknown state. readyOps="+ops);
+            throw new IOException("Data is in unknown state. readyOps="+ops);
+        }//end if
+        return false;
+    }
+
+    private void completeConnect() throws SocketException {
+        //we connected, register ourselves for writing
+        setConnected(true);
+        connecting = false;
+        setRequestCount(0);
+        setConnectTime(System.currentTimeMillis());
+        if (socketChannel!=null) {
+            socketChannel.socket().setSendBufferSize(getTxBufSize());
+            socketChannel.socket().setReceiveBufferSize(getRxBufSize());
+            socketChannel.socket().setSoTimeout((int)getTimeout());
+            socketChannel.socket().setSoLinger(getSoLingerOn(),getSoLingerOn()?getSoLingerTime():0);
+            socketChannel.socket().setTcpNoDelay(getTcpNoDelay());
+            socketChannel.socket().setKeepAlive(getSoKeepAlive());
+            socketChannel.socket().setReuseAddress(getSoReuseAddress());
+            socketChannel.socket().setOOBInline(getOoBInline());
+            socketChannel.socket().setSoLinger(getSoLingerOn(),getSoLingerTime());
+            socketChannel.socket().setTrafficClass(getSoTrafficClass());
+        } else if (dataChannel!=null) {
+            dataChannel.socket().setSendBufferSize(getUdpTxBufSize());
+            dataChannel.socket().setReceiveBufferSize(getUdpRxBufSize());
+            dataChannel.socket().setSoTimeout((int)getTimeout());
+            dataChannel.socket().setReuseAddress(getSoReuseAddress());
+            dataChannel.socket().setTrafficClass(getSoTrafficClass());
+        }
+    }
+
+
+
+    protected boolean read(SelectionKey key) throws IOException {
+        //if there is no message here, we are done
+        if ( current == null ) return true;
+        int read = isUdpBased()?dataChannel.read(readbuf) : socketChannel.read(readbuf);
+        //end of stream
+        if ( read == -1 ) throw new IOException("Unable to receive an ack message. EOF on socket channel has been reached.");
+        //no data read
+        else if ( read == 0 ) return false;
+        readbuf.flip();
+        ackbuf.append(readbuf,read);
+        readbuf.clear();
+        if (ackbuf.doesPackageExist() ) {
+            byte[] ackcmd = ackbuf.extractDataPackage(true).getBytes();
+            boolean ack = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.ACK_DATA);
+            boolean fack = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA);
+            if ( fack && getThrowOnFailedAck() ) throw new RemoteProcessException("Received a failed ack:org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA");
+            return ack || fack;
+        } else {
+            return false;
+        }
+    }
+
+
+    protected boolean write(SelectionKey key) throws IOException {
+        if ( (!isConnected()) || (this.socketChannel==null && this.dataChannel==null)) {
+            throw new IOException("NioSender is not connected, this should not occur.");
+        }
+        if ( current != null ) {
+            if ( remaining > 0 ) {
+                //we have written everything, or we are starting a new package
+                //protect against buffer overwrite
+                int byteswritten = isUdpBased()?dataChannel.write(writebuf) : socketChannel.write(writebuf);
+                if (byteswritten == -1 ) throw new EOFException();
+                remaining -= byteswritten;
+                //if the entire message was written from the buffer
+                //reset the position counter
+                if ( remaining < 0 ) {
+                    remaining = 0;
+                }
+            }
+            return (remaining==0);
+        }
+        //no message to send, we can consider that complete
+        return true;
+    }
+
+    /**
+     * connect - blocking in this operation
+     *
+     * @throws IOException
+     * TODO Implement this org.apache.catalina.tribes.transport.IDataSender method
+     */
+    @Override
+    public synchronized void connect() throws IOException {
+        if ( connecting || isConnected()) return;
+        connecting = true;
+        if ( isConnected() ) throw new IOException("NioSender is already in connected state.");
+        if ( readbuf == null ) {
+            readbuf = getReadBuffer();
+        } else {
+            readbuf.clear();
+        }
+        if ( writebuf == null ) {
+            writebuf = getWriteBuffer();
+        } else {
+            writebuf.clear();
+        }
+
+        if (isUdpBased()) {
+            InetSocketAddress daddr = new InetSocketAddress(getAddress(),getUdpPort());
+            if ( dataChannel != null ) throw new IOException("Datagram channel has already been established. Connection might be in progress.");
+            dataChannel = DatagramChannel.open();
+            dataChannel.configureBlocking(false);
+            dataChannel.connect(daddr);
+            completeConnect();
+            dataChannel.register(getSelector(),SelectionKey.OP_WRITE, this);
+            
+        } else {
+            InetSocketAddress addr = new InetSocketAddress(getAddress(),getPort());
+            if ( socketChannel != null ) throw new IOException("Socket channel has already been established. Connection might be in progress.");
+            socketChannel = SocketChannel.open();
+            socketChannel.configureBlocking(false);
+            if ( socketChannel.connect(addr) ) {
+                completeConnect();
+                socketChannel.register(getSelector(), SelectionKey.OP_WRITE, this);
+            } else {
+                socketChannel.register(getSelector(), SelectionKey.OP_CONNECT, this);
+            }
+        }
+    }
+
+
+    /**
+     * disconnect
+     *
+     * TODO Implement this org.apache.catalina.tribes.transport.IDataSender method
+     */
+    @Override
+    public void disconnect() {
+        try {
+            connecting = false;
+            setConnected(false);
+            if ( socketChannel != null ) {
+                try {
+                    try {socketChannel.socket().close();}catch ( Exception x){}
+                    //error free close, all the way
+                    //try {socket.shutdownOutput();}catch ( Exception x){}
+                    //try {socket.shutdownInput();}catch ( Exception x){}
+                    //try {socket.close();}catch ( Exception x){}
+                    try {socketChannel.close();}catch ( Exception x){}
+                }finally {
+                    socketChannel = null;
+                }
+            }
+            if ( dataChannel != null ) {
+                try {
+                    try {dataChannel.socket().close();}catch ( Exception x){}
+                    //error free close, all the way
+                    //try {socket.shutdownOutput();}catch ( Exception x){}
+                    //try {socket.shutdownInput();}catch ( Exception x){}
+                    //try {socket.close();}catch ( Exception x){}
+                    try {dataChannel.close();}catch ( Exception x){}
+                }finally {
+                    dataChannel = null;
+                }
+            }
+        } catch ( Exception x ) {
+            log.error("Unable to disconnect NioSender. msg="+x.getMessage());
+            if ( log.isDebugEnabled() ) log.debug("Unable to disconnect NioSender. msg="+x.getMessage(),x);
+        } finally {
+        }
+
+    }
+
+    public void reset() {
+        if ( isConnected() && readbuf == null) {
+            readbuf = getReadBuffer();
+        }
+        if ( readbuf != null ) readbuf.clear();
+        if ( writebuf != null ) writebuf.clear();
+        current = null;
+        ackbuf.clear();
+        remaining = 0;
+        complete = false;
+        setAttempt(0);
+        setRequestCount(0);
+        setConnectTime(-1);
+        setUdpBased(false);
+    }
+
+    private ByteBuffer getReadBuffer() {
+        return getBuffer(getRxBufSize());
+    }
+
+    private ByteBuffer getWriteBuffer() {
+        return getBuffer(getTxBufSize());
+    }
+
+    private ByteBuffer getBuffer(int size) {
+        return (getDirectBuffer()?ByteBuffer.allocateDirect(size):ByteBuffer.allocate(size));
+    }
+
+    /**
+    * sendMessage
+    *
+    * @param data ChannelMessage
+    * @throws IOException
+    * TODO Implement this org.apache.catalina.tribes.transport.IDataSender method
+    */
+   public synchronized void setMessage(byte[] data) throws IOException {
+       setMessage(data,0,data.length);
+   }
+
+   public synchronized void setMessage(byte[] data,int offset, int length) throws IOException {
+       if ( data != null ) {
+           current = data;
+           remaining = length;
+           ackbuf.clear();
+           if ( writebuf != null ) writebuf.clear();
+           else writebuf = getBuffer(length);
+           if ( writebuf.capacity() < length ) writebuf = getBuffer(length);
+           
+           //TODO use ByteBuffer.wrap to avoid copying the data.
+           writebuf.put(data,offset,length);
+           //writebuf.rewind();
+           //set the limit so that we don't write non wanted data
+           //writebuf.limit(length);
+           writebuf.flip();
+           if (isConnected()) {
+               if (isUdpBased())
+                   dataChannel.register(getSelector(), SelectionKey.OP_WRITE, this);
+               else
+                   socketChannel.register(getSelector(), SelectionKey.OP_WRITE, this);
+           }
+       }
+   }
+
+   public byte[] getMessage() {
+       return current;
+   }
+
+
+
+    public boolean isComplete() {
+        return complete;
+    }
+
+    public Selector getSelector() {
+        return selector;
+    }
+
+    public void setSelector(Selector selector) {
+        this.selector = selector;
+    }
+
+
+    public void setComplete(boolean complete) {
+        this.complete = complete;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java
new file mode 100644
index 0000000..e855a37
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.transport.nio;
+
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import org.apache.catalina.tribes.transport.MultiPointSender;
+import org.apache.catalina.tribes.transport.SenderState;
+import org.apache.catalina.tribes.util.Logs;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class ParallelNioSender extends AbstractSender implements MultiPointSender {
+
+    private static final Log log = LogFactory.getLog(ParallelNioSender.class);
+    protected long selectTimeout = 5000; //default 5 seconds, same as send timeout
+    protected Selector selector;
+    protected HashMap<Member, NioSender> nioSenders =
+        new HashMap<Member, NioSender>();
+
+    public ParallelNioSender() throws IOException {
+        selector = Selector.open();
+        setConnected(true);
+    }
+
+
+    @Override
+    public synchronized void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+        long start = System.currentTimeMillis();
+        this.setUdpBased((msg.getOptions()&Channel.SEND_OPTIONS_UDP) == Channel.SEND_OPTIONS_UDP);
+        byte[] data = XByteBuffer.createDataPackage((ChannelData)msg);
+        NioSender[] senders = setupForSend(destination);
+        connect(senders);
+        setData(senders,data);
+
+        int remaining = senders.length;
+        ChannelException cx = null;
+        try {
+            //loop until complete, an error happens, or we timeout
+            long delta = System.currentTimeMillis() - start;
+            boolean waitForAck = (Channel.SEND_OPTIONS_USE_ACK & msg.getOptions()) == Channel.SEND_OPTIONS_USE_ACK;
+            while ( (remaining>0) && (delta<getTimeout()) ) {
+                try {
+                    remaining -= doLoop(selectTimeout, getMaxRetryAttempts(),waitForAck,msg);
+                } catch (Exception x ) {
+                    if (log.isTraceEnabled()) log.trace("Error sending message", x);
+                    int faulty = (cx == null)?0:cx.getFaultyMembers().length;
+                    if ( cx == null ) {
+                        if ( x instanceof ChannelException ) cx = (ChannelException)x;
+                        else cx = new ChannelException("Parallel NIO send failed.", x);
+                    } else {
+                        if (x instanceof ChannelException) cx.addFaultyMember( ( (ChannelException) x).getFaultyMembers());
+                    }
+                    //count down the remaining on an error
+                    if (faulty<cx.getFaultyMembers().length) remaining -= (cx.getFaultyMembers().length-faulty);
+                }
+                //bail out if all remaining senders are failing
+                if ( cx != null && cx.getFaultyMembers().length == remaining ) throw cx;
+                delta = System.currentTimeMillis() - start;
+            }
+            if ( remaining > 0 ) {
+                //timeout has occurred
+                ChannelException cxtimeout = new ChannelException("Operation has timed out("+getTimeout()+" ms.).");
+                if ( cx==null ) cx = new ChannelException("Operation has timed out("+getTimeout()+" ms.).");
+                for (int i=0; i<senders.length; i++ ) {
+                    if (!senders[i].isComplete() ) cx.addFaultyMember(senders[i].getDestination(),cxtimeout);
+                }
+                throw cx;
+            } else if ( cx != null ) {
+                //there was an error
+                throw cx;
+            }
+        } catch (Exception x ) {
+            try { this.disconnect(); } catch (Exception e) {/*Ignore*/}
+            if ( x instanceof ChannelException ) throw (ChannelException)x;
+            else throw new ChannelException(x);
+        }
+
+    }
+
+    private int doLoop(long selectTimeOut, int maxAttempts, boolean waitForAck, ChannelMessage msg) throws IOException, ChannelException {
+        int completed = 0;
+        int selectedKeys = selector.select(selectTimeOut);
+
+        if (selectedKeys == 0) {
+            return 0;
+        }
+
+        Iterator<SelectionKey> it = selector.selectedKeys().iterator();
+        while (it.hasNext()) {
+            SelectionKey sk = it.next();
+            it.remove();
+            int readyOps = sk.readyOps();
+            sk.interestOps(sk.interestOps() & ~readyOps);
+            NioSender sender = (NioSender) sk.attachment();
+            try {
+                if (sender.process(sk,waitForAck)) {
+                    completed++;
+                    sender.setComplete(true);
+                    if ( Logs.MESSAGES.isTraceEnabled() ) {
+                        Logs.MESSAGES.trace("ParallelNioSender - Sent msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+sender.getDestination().getName());
+                    }
+                    SenderState.getSenderState(sender.getDestination()).setReady();
+                }//end if
+            } catch (Exception x) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Error while processing send to " + sender.getDestination().getName(), x);
+                }
+                SenderState state = SenderState.getSenderState(sender.getDestination());
+                int attempt = sender.getAttempt()+1;
+                boolean retry = (sender.getAttempt() <= maxAttempts && maxAttempts>0);
+                synchronized (state) {
+
+                    //sk.cancel();
+                    if (state.isSuspect()) state.setFailing();
+                    if (state.isReady()) {
+                        state.setSuspect();
+                        if ( retry )
+                            log.warn("Member send is failing for:" + sender.getDestination().getName() +" ; Setting to suspect and retrying.");
+                        else
+                            log.warn("Member send is failing for:" + sender.getDestination().getName() +" ; Setting to suspect.", x);
+                    }
+                }
+                if ( !isConnected() ) {
+                    log.warn("Not retrying send for:" + sender.getDestination().getName() + "; Sender is disconnected.");
+                    ChannelException cx = new ChannelException("Send failed, and sender is disconnected. Not retrying.",x);
+                    cx.addFaultyMember(sender.getDestination(),x);
+                    throw cx;
+                }
+
+                byte[] data = sender.getMessage();
+                if ( retry ) {
+                    try {
+                        sender.disconnect();
+                        sender.connect();
+                        sender.setAttempt(attempt);
+                        sender.setMessage(data);
+                    }catch ( Exception ignore){
+                        state.setFailing();
+                    }
+                } else {
+                    ChannelException cx = new ChannelException("Send failed, attempt:"+sender.getAttempt()+" max:"+maxAttempts,x);
+                    cx.addFaultyMember(sender.getDestination(),x);
+                    throw cx;
+                }//end if
+            }
+        }
+        return completed;
+
+    }
+
+    private void connect(NioSender[] senders) throws ChannelException {
+        ChannelException x = null;
+        for (int i=0; i<senders.length; i++ ) {
+            try {
+                senders[i].connect();
+            }catch ( IOException io ) {
+                if ( x==null ) x = new ChannelException(io);
+                x.addFaultyMember(senders[i].getDestination(),io);
+            }
+        }
+        if ( x != null ) throw x;
+    }
+
+    private void setData(NioSender[] senders, byte[] data) throws ChannelException {
+        ChannelException x = null;
+        for (int i=0; i<senders.length; i++ ) {
+            try {
+                senders[i].setMessage(data);
+            }catch ( IOException io ) {
+                if ( x==null ) x = new ChannelException(io);
+                x.addFaultyMember(senders[i].getDestination(),io);
+            }
+        }
+        if ( x != null ) throw x;
+    }
+
+
+    private NioSender[] setupForSend(Member[] destination) throws ChannelException {
+        ChannelException cx = null;
+        NioSender[] result = new NioSender[destination.length];
+        for ( int i=0; i<destination.length; i++ ) {
+            NioSender sender = nioSenders.get(destination[i]);
+            try {
+
+                if (sender == null) {
+                    sender = new NioSender();
+                    AbstractSender.transferProperties(this, sender);
+                    nioSenders.put(destination[i], sender);
+                }
+                sender.reset();
+                sender.setDestination(destination[i]);
+                sender.setSelector(selector);
+                sender.setUdpBased(isUdpBased());
+                result[i] = sender;
+            }catch ( UnknownHostException x ) {
+                if (cx == null) cx = new ChannelException("Unable to setup NioSender.", x);
+                cx.addFaultyMember(destination[i], x);
+            }
+        }
+        if ( cx != null ) throw cx;
+        else return result;
+    }
+
+    @Override
+    public void connect() {
+        //do nothing, we connect on demand
+        setConnected(true);
+    }
+
+
+    private synchronized void close() throws ChannelException  {
+        ChannelException x = null;
+        Object[] members = nioSenders.keySet().toArray();
+        for (int i=0; i<members.length; i++ ) {
+            Member mbr = (Member)members[i];
+            try {
+                NioSender sender = nioSenders.get(mbr);
+                sender.disconnect();
+            }catch ( Exception e ) {
+                if ( x == null ) x = new ChannelException(e);
+                x.addFaultyMember(mbr,e);
+            }
+            nioSenders.remove(mbr);
+        }
+        if ( x != null ) throw x;
+    }
+
+    @Override
+    public void add(Member member) {
+        // NOOP
+    }
+
+    @Override
+    public void remove(Member member) {
+        //disconnect senders
+        NioSender sender = nioSenders.remove(member);
+        if ( sender != null ) sender.disconnect();
+    }
+
+
+    @Override
+    public synchronized void disconnect() {
+        setConnected(false);
+        try {close(); }catch (Exception x){/*Ignore*/}
+    }
+
+    @Override
+    public void finalize() {
+        try {disconnect(); }catch ( Exception e){/*Ignore*/}
+        try {
+            selector.close();
+        }catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Failed to close selector", e);
+            }
+        }
+    }
+
+    @Override
+    public boolean keepalive() {
+        boolean result = false;
+        for ( Iterator<Entry<Member, NioSender>> i = nioSenders.entrySet().iterator(); i.hasNext();) {
+            Map.Entry<Member, NioSender> entry = i.next();
+            NioSender sender = entry.getValue();
+            if ( sender.keepalive() ) {
+                //nioSenders.remove(entry.getKey());
+                i.remove();
+                result = true;
+            } else {
+                try {
+                    sender.read(null);
+                }catch ( IOException x ) {
+                    sender.disconnect();
+                    sender.reset();
+                    //nioSenders.remove(entry.getKey());
+                    i.remove();
+                    result = true;
+                }catch ( Exception x ) {
+                    log.warn("Error during keepalive test for sender:"+sender,x);
+                }
+            }
+        }
+        //clean up any cancelled keys
+        if ( result ) try { selector.selectNow(); }catch (Exception e){/*Ignore*/}
+        return result;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/PooledParallelSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/PooledParallelSender.java
new file mode 100644
index 0000000..43e2b1f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/nio/PooledParallelSender.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.transport.nio;
+
+import java.io.IOException;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import org.apache.catalina.tribes.transport.DataSender;
+import org.apache.catalina.tribes.transport.PooledSender;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class PooledParallelSender extends PooledSender {
+    protected boolean connected = true;
+    public PooledParallelSender() {
+        super();
+    }
+    
+    public void sendMessage(Member[] destination, ChannelMessage message) throws ChannelException {
+        if ( !connected ) throw new ChannelException("Sender not connected.");
+        ParallelNioSender sender = (ParallelNioSender)getSender();
+        if (sender == null) {
+            ChannelException cx = new ChannelException("Unable to retrieve a data sender, time out error.");
+            for (int i = 0; i < destination.length; i++) cx.addFaultyMember(destination[i], new NullPointerException("Unable to retrieve a sender from the sender pool"));
+            throw cx;
+        } else {
+            try {
+                sender.sendMessage(destination, message);
+                sender.keepalive();
+            } catch (ChannelException x) {
+                sender.disconnect();
+                throw x;
+            } finally {
+                returnSender(sender);
+                if (!connected) disconnect();
+            }
+        }
+    }
+
+    @Override
+    public DataSender getNewDataSender() {
+        try {
+            ParallelNioSender sender = new ParallelNioSender();
+            AbstractSender.transferProperties(this,sender);
+            return sender;
+        } catch ( IOException x ) {
+            throw new RuntimeException("Unable to open NIO selector.",x);
+        }
+    }
+    
+    @Override
+    public synchronized void disconnect() {
+        this.connected = false;
+        super.disconnect();
+    }
+
+    @Override
+    public synchronized void connect() throws IOException {
+        this.connected = true;
+        super.connect();
+    }
+   
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/Arrays.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/Arrays.java
new file mode 100644
index 0000000..6706f10
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/Arrays.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.util;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.group.AbsoluteOrder;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.Membership;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class Arrays {
+    private static final Log log = LogFactory.getLog(Arrays.class);
+    
+    public static boolean contains(byte[] source, int srcoffset, byte[] key, int keyoffset, int length) {
+        if ( srcoffset < 0 || srcoffset >= source.length) throw new ArrayIndexOutOfBoundsException("srcoffset is out of bounds.");
+        if ( keyoffset < 0 || keyoffset >= key.length) throw new ArrayIndexOutOfBoundsException("keyoffset is out of bounds.");
+        if ( length > (key.length-keyoffset) ) throw new ArrayIndexOutOfBoundsException("not enough data elements in the key, length is out of bounds.");
+        //we don't have enough data to validate it
+        if ( length > (source.length-srcoffset) ) return false;
+        boolean match = true;
+        int pos = keyoffset;
+        for ( int i=srcoffset; match && i<length; i++ ) {
+            match = (source[i] == key[pos++]);
+        }
+        return match;
+    }
+    
+    public static String toString(byte[] data) {
+        return toString(data,0,data!=null?data.length:0);
+    }
+
+    public static String toString(byte[] data, int offset, int length) {
+        return toString(data,offset,length,false);
+    }
+
+    public static String toString(byte[] data, int offset, int length, boolean unsigned) {
+        StringBuilder buf = new StringBuilder("{");
+        if ( data != null && length > 0 ) {
+            int i = offset;
+            if (unsigned) {
+                buf.append(data[i++] & 0xff);
+                for (; i < length; i++) {
+                    buf.append(", ").append(data[i] & 0xff);
+                }
+            } else {
+                buf.append(data[i++]);
+                for (; i < length; i++) {
+                    buf.append(", ").append(data[i]);
+                }
+            }
+        }
+        buf.append("}");
+        return buf.toString();
+    }
+
+    public static String toString(Object[] data) {
+        return toString(data,0,data!=null?data.length:0);
+    }
+    
+    public static String toString(Object[] data, int offset, int length) {
+        StringBuilder buf = new StringBuilder("{");
+        if ( data != null && length > 0 ) {
+            buf.append(data[offset++]);
+            for (int i = offset; i < length; i++) {
+                buf.append(", ").append(data[i]);
+            }
+        }
+        buf.append("}");
+        return buf.toString();
+    }
+    
+    public static String toNameString(Member[] data) {
+        return toNameString(data,0,data!=null?data.length:0);
+    }
+    
+    public static String toNameString(Member[] data, int offset, int length) {
+        StringBuilder buf = new StringBuilder("{");
+        if ( data != null && length > 0 ) {
+            buf.append(data[offset++].getName());
+            for (int i = offset; i < length; i++) {
+                buf.append(", ").append(data[i].getName());
+            }
+        }
+        buf.append("}");
+        return buf.toString();
+    }
+
+    public static int add(int[] data) {
+        int result = 0;
+        for (int i=0;i<data.length; i++ ) result += data[i];
+        return result;
+    }
+    
+    public static UniqueId getUniqudId(ChannelMessage msg) {
+        return new UniqueId(msg.getUniqueId());
+    }
+
+    public static UniqueId getUniqudId(byte[] data) {
+        return new UniqueId(data);
+    }
+    
+    public static boolean equals(byte[] o1, byte[] o2) {
+        return java.util.Arrays.equals(o1,o2);
+    }
+
+    public static boolean equals(Object[] o1, Object[] o2) {
+        boolean result = o1.length == o2.length;
+        if ( result ) for (int i=0; i<o1.length && result; i++ ) result = o1[i].equals(o2[i]);
+        return result;
+    }
+    
+    public static boolean sameMembers(Member[] m1, Member[] m2) {
+        AbsoluteOrder.absoluteOrder(m1);
+        AbsoluteOrder.absoluteOrder(m2);
+        return equals(m1,m2);
+    }
+    
+    public static Member[] merge(Member[] m1, Member[] m2) {
+        AbsoluteOrder.absoluteOrder(m1);
+        AbsoluteOrder.absoluteOrder(m2);
+        ArrayList<Member> list =
+            new ArrayList<Member>(java.util.Arrays.asList(m1));
+        for (int i=0; i<m2.length; i++) if ( !list.contains(m2[i]) ) list.add(m2[i]);
+        Member[] result = new Member[list.size()];
+        list.toArray(result);
+        AbsoluteOrder.absoluteOrder(result);
+        return result;
+    }
+    
+    public static void fill(Membership mbrship, Member[] m) {
+        for (int i=0; i<m.length; i++ ) mbrship.addMember((MemberImpl)m[i]);
+    }
+    
+    public static Member[] diff(Membership complete, Membership local, MemberImpl ignore) {
+        ArrayList<Member> result = new ArrayList<Member>();
+        MemberImpl[] comp = complete.getMembers();
+        for ( int i=0; i<comp.length; i++ ) {
+            if ( ignore!=null && ignore.equals(comp[i]) ) continue;
+            if ( local.getMember(comp[i]) == null ) result.add(comp[i]);
+        }
+        return result.toArray(new MemberImpl[result.size()]);
+    }
+    
+    public static Member[] remove(Member[] all, Member remove) {
+        return extract(all,new Member[] {remove});
+    }
+    
+    public static Member[] extract(Member[] all, Member[] remove) {
+        List<Member> alist = java.util.Arrays.asList(all);
+        ArrayList<Member> list = new ArrayList<Member>(alist);
+        for (int i=0; i<remove.length; i++ ) list.remove(remove[i]);
+        return list.toArray(new Member[list.size()]);
+    }
+    
+    public static int indexOf(Member member, Member[] members) {
+        int result = -1;
+        for (int i=0; (result==-1) && (i<members.length); i++ ) 
+            if ( member.equals(members[i]) ) result = i;
+        return result;
+    }
+    
+    public static int nextIndex(Member member, Member[] members) {
+        int idx = indexOf(member,members)+1;
+        if (idx >= members.length ) idx = ((members.length>0)?0:-1);
+        
+//System.out.println("Next index:"+idx);
+//System.out.println("Member:"+member.getName());
+//System.out.println("Members:"+toNameString(members));
+        return idx;
+    }
+    
+    public static int hashCode(byte a[]) {
+        if (a == null)
+            return 0;
+
+        int result = 1;
+        for (int i=0; i<a.length; i++) {
+            byte element = a[i];
+            result = 31 * result + element;
+        }
+        return result;
+    }
+    
+    public static byte[] fromString(String value) { 
+        if ( value == null ) return null;
+        if ( !value.startsWith("{") ) throw new RuntimeException("byte arrays must be represented as {1,3,4,5,6}");
+        StringTokenizer t = new StringTokenizer(value,"{,}",false);
+        byte[] result = new byte[t.countTokens()];
+        for (int i=0; i<result.length; i++ ) result[i] = Byte.parseByte(t.nextToken());
+        return result;
+    }
+
+
+    public static byte[] convert(String s) {
+        try {
+            return s.getBytes("ISO-8859-1");
+        }catch (UnsupportedEncodingException ux ) {
+            log.error("Unable to convert ["+s+"] into a byte[] using ISO-8859-1 encoding, falling back to default encoding.");
+            return s.getBytes();
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/ExecutorFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/ExecutorFactory.java
new file mode 100644
index 0000000..ca33d02
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/ExecutorFactory.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.util;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class ExecutorFactory {
+
+    public static ExecutorService newThreadPool(int minThreads, int maxThreads, long maxIdleTime, TimeUnit unit) {
+        TaskQueue taskqueue = new TaskQueue();
+        ThreadPoolExecutor service = new ThreadPoolExecutor(minThreads, maxThreads, maxIdleTime, unit,taskqueue);
+        taskqueue.setParent(service);
+        return service;
+    }
+
+    public static ExecutorService newThreadPool(int minThreads, int maxThreads, long maxIdleTime, TimeUnit unit, ThreadFactory threadFactory) {
+        TaskQueue taskqueue = new TaskQueue();
+        ThreadPoolExecutor service = new ThreadPoolExecutor(minThreads, maxThreads, maxIdleTime, unit,taskqueue, threadFactory);
+        taskqueue.setParent(service);
+        return service;
+    }
+    
+     // ---------------------------------------------- TaskQueue Inner Class
+    private static class TaskQueue extends LinkedBlockingQueue<Runnable> {
+        private static final long serialVersionUID = 1L;
+
+        ThreadPoolExecutor parent = null;
+
+        public TaskQueue() {
+            super();
+        }
+
+        public TaskQueue(int initialCapacity) {
+            super(initialCapacity);
+        }
+
+        public TaskQueue(Collection<? extends Runnable> c) {
+            super(c);
+        }
+
+        public void setParent(ThreadPoolExecutor tp) {
+            parent = tp;
+        }
+        
+        public boolean force(Runnable o) {
+            if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
+            return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
+        }
+
+        @Override
+        public boolean offer(Runnable o) {
+            //we can't do any checks
+            if (parent==null) return super.offer(o);
+            //we are maxed out on threads, simply queue the object
+            if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
+            //we have idle threads, just add it to the queue
+            //this is an approximation, so it could use some tuning
+            if (parent.getActiveCount()<(parent.getPoolSize())) return super.offer(o);
+            //if we have less threads than maximum force creation of a new thread
+            if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
+            //if we reached here, we need to add it to the queue
+            return super.offer(o);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/LocalStrings.properties
new file mode 100644
index 0000000..18a0566
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/LocalStrings.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+uuidGenerator.createRandom=Creation of SecureRandom instance for UUID generation using [{0}] took [{1}] milliseconds.
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/Logs.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/Logs.java
new file mode 100644
index 0000000..1655f45
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/Logs.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.util;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+/**
+ * 
+ * Simple class that holds references to global loggers
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class Logs {
+    public static final Log MESSAGES = LogFactory.getLog( "org.apache.catalina.tribes.MESSAGES" );
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/StringManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/StringManager.java
new file mode 100644
index 0000000..526d9a1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/StringManager.java
@@ -0,0 +1,171 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.catalina.tribes.util;
+
+import java.text.MessageFormat;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * An internationalization / localization helper class which reduces
+ * the bother of handling ResourceBundles and takes care of the
+ * common cases of message formating which otherwise require the
+ * creation of Object arrays and such.
+ *
+ * <p>The StringManager operates on a package basis. One StringManager
+ * per package can be created and accessed via the getManager method
+ * call.
+ *
+ * <p>The StringManager will look for a ResourceBundle named by
+ * the package name given plus the suffix of "LocalStrings". In
+ * practice, this means that the localized information will be contained
+ * in a LocalStrings.properties file located in the package
+ * directory of the classpath.
+ *
+ * <p>Please see the documentation for java.util.ResourceBundle for
+ * more information.
+ *
+ * @version $Id: StringManager.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ *
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Mel Martinez [mmartinez@g1440.com]
+ * @see java.util.ResourceBundle
+ */
+
+public class StringManager {
+
+    /**
+     * The ResourceBundle for this StringManager.
+     */
+    private ResourceBundle bundle;
+    private Locale locale;
+
+    /**
+     * Creates a new StringManager for a given package. This is a
+     * private method and all access to it is arbitrated by the
+     * static getManager method call so that only one StringManager
+     * per package will be created.
+     *
+     * @param packageName Name of package to create StringManager for.
+     */
+    private StringManager(String packageName) {
+        String bundleName = packageName + ".LocalStrings";
+        try {
+            bundle = ResourceBundle.getBundle(bundleName, Locale.getDefault());
+        } catch( MissingResourceException ex ) {
+            // Try from the current loader (that's the case for trusted apps)
+            // Should only be required if using a TC5 style classloader structure
+            // where common != shared != server
+            ClassLoader cl = Thread.currentThread().getContextClassLoader();
+            if( cl != null ) {
+                try {
+                    bundle = ResourceBundle.getBundle(
+                            bundleName, Locale.getDefault(), cl);
+                } catch(MissingResourceException ex2) {
+                    // Ignore
+                }
+            }
+        }
+        // Get the actual locale, which may be different from the requested one
+        if (bundle != null) {
+            locale = bundle.getLocale();
+        }
+    }
+
+    /**
+        Get a string from the underlying resource bundle or return
+        null if the String is not found.
+     
+        @param key to desired resource String
+        @return resource String matching <i>key</i> from underlying
+                bundle or null if not found.
+        @throws IllegalArgumentException if <i>key</i> is null.        
+     */
+    public String getString(String key) {
+        if(key == null){
+            String msg = "key may not have a null value";
+
+            throw new IllegalArgumentException(msg);
+        }
+
+        String str = null;
+
+        try {
+            str = bundle.getString(key);
+        } catch(MissingResourceException mre) {
+            //bad: shouldn't mask an exception the following way:
+            //   str = "[cannot find message associated with key '" + key + "' due to " + mre + "]";
+            //     because it hides the fact that the String was missing
+            //     from the calling code.
+            //good: could just throw the exception (or wrap it in another)
+            //      but that would probably cause much havoc on existing
+            //      code.
+            //better: consistent with container pattern to
+            //      simply return null.  Calling code can then do
+            //      a null check.
+            str = null;
+        }
+
+        return str;
+    }
+
+    /**
+     * Get a string from the underlying resource bundle and format
+     * it with the given set of arguments.
+     *
+     * @param key
+     * @param args
+     */
+    public String getString(final String key, final Object... args) {
+        String value = getString(key);
+        if (value == null) {
+            value = key;
+        }
+
+        MessageFormat mf = new MessageFormat(value);
+        mf.setLocale(locale);
+        return mf.format(args, new StringBuffer(), null).toString();
+    }
+
+    // --------------------------------------------------------------
+    // STATIC SUPPORT METHODS
+    // --------------------------------------------------------------
+
+    private static Hashtable<String, StringManager> managers =
+        new Hashtable<String, StringManager>();
+
+    /**
+     * Get the StringManager for a particular package. If a manager for
+     * a package already exists, it will be reused, else a new
+     * StringManager will be created and returned.
+     *
+     * @param packageName The package name
+     */
+    public static final synchronized StringManager getManager(String packageName) {
+        StringManager mgr = managers.get(packageName);
+        if (mgr == null) {
+            mgr = new StringManager(packageName);
+            managers.put(packageName, mgr);
+        }
+        return mgr;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/TcclThreadFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/TcclThreadFactory.java
new file mode 100644
index 0000000..a1959f9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/TcclThreadFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * ThreadFactory implementation that creates threads with the thread context
+ * class loader set to the class loader that loaded this factory. It is intended
+ * to be used when tasks may be passed to executors when the web application
+ * class loader is set as the thread context class loader, such as in async
+ * session replication.
+ */
+public class TcclThreadFactory implements ThreadFactory {
+    
+    private static final AtomicInteger poolNumber = new AtomicInteger(1);
+    private static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+    
+    private final ThreadGroup group;
+    private final AtomicInteger threadNumber = new AtomicInteger(1);
+    private final String namePrefix;
+    
+    public TcclThreadFactory() {
+        SecurityManager s = System.getSecurityManager();
+        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+        namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
+    }
+
+    @Override
+    public Thread newThread(Runnable r) {
+        final Thread t = new Thread(group, r, namePrefix +
+                threadNumber.getAndIncrement());
+        
+        if (IS_SECURITY_ENABLED) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    t.setContextClassLoader(this.getClass().getClassLoader());
+                    return null;
+                }
+            });
+        } else {
+            t.setContextClassLoader(this.getClass().getClassLoader());
+        }
+        return t;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/UUIDGenerator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/UUIDGenerator.java
new file mode 100644
index 0000000..581e65c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/util/UUIDGenerator.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.util;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * simple generation of a UUID 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class UUIDGenerator {
+    private static final Log log = LogFactory.getLog(UUIDGenerator.class);
+    protected static final StringManager sm =
+        StringManager.getManager("org.apache.catalina.tribes.util");
+
+    public static final int UUID_LENGTH = 16;
+    public static final int UUID_VERSION = 4;
+    public static final int BYTES_PER_INT = 4;
+    public static final int BITS_PER_BYTE = 8;
+    
+    protected static SecureRandom secrand = null;
+    protected static Random rand = new Random();
+    
+    static {
+        long start = System.currentTimeMillis();
+        secrand = new SecureRandom();
+        // seed the generator
+        secrand.nextInt();
+        long time = System.currentTimeMillis() - start;
+        if (time > 100) {
+            log.info(sm.getString("uuidGenerator.createRandom",
+                    secrand.getAlgorithm(), Long.valueOf(time)));
+        }
+    }
+    
+    public static byte[] randomUUID(boolean secure) {
+        byte[] result = new byte[UUID_LENGTH];
+        return randomUUID(secure,result,0);
+    }
+
+    public static byte[] randomUUID(boolean secure, byte[] into, int offset) {
+        if ( (offset+UUID_LENGTH)>into.length )
+            throw new ArrayIndexOutOfBoundsException("Unable to fit "+UUID_LENGTH+" bytes into the array. length:"+into.length+" required length:"+(offset+UUID_LENGTH));
+        Random r = (secure&&(secrand!=null))?secrand:rand;
+        nextBytes(into,offset,UUID_LENGTH,r);
+        into[6+offset] &= 0x0F;
+        into[6+offset] |= (UUID_VERSION << 4);
+        into[8+offset] &= 0x3F; //0011 1111
+        into[8+offset] |= 0x80; //1000 0000
+        return into;
+    }
+    
+    /**
+     * Same as java.util.Random.nextBytes except this one we dont have to allocate a new byte array
+     * @param into byte[]
+     * @param offset int
+     * @param length int
+     * @param r Random
+     */
+    public static void nextBytes(byte[] into, int offset, int length, Random r) {
+        int numRequested = length;
+        int numGot = 0, rnd = 0;
+        while (true) {
+            for (int i = 0; i < BYTES_PER_INT; i++) {
+                if (numGot == numRequested) return;
+                rnd = (i == 0 ? r.nextInt() : rnd >> BITS_PER_BYTE);
+                into[offset+numGot] = (byte) rnd;
+                numGot++;
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AbstractAjpProcessor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AbstractAjpProcessor.java
new file mode 100644
index 0000000..7e5b86e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AbstractAjpProcessor.java
@@ -0,0 +1,873 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.ajp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.ActionHook;
+import org.apache.coyote.Adapter;
+import org.apache.coyote.AsyncContextCallback;
+import org.apache.coyote.AsyncStateMachine;
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Processor;
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.HttpMessages;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Base class for AJP Processor implementations.
+ */
+public abstract class AbstractAjpProcessor implements ActionHook, Processor {
+
+    protected abstract Log getLog();
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated adapter.
+     */
+    protected Adapter adapter = null;
+
+
+    /**
+     * Associated endpoint.
+     */
+    protected AbstractEndpoint endpoint;
+
+
+    /**
+     * Request object.
+     */
+    protected Request request = null;
+
+
+    /**
+     * Response object.
+     */
+    protected Response response = null;
+
+
+    /**
+     * AJP packet size.
+     */
+    protected int packetSize;
+
+    /**
+     * Header message. Note that this header is merely the one used during the
+     * processing of the first message of a "request", so it might not be a
+     * request header. It will stay unchanged during the processing of the whole
+     * request.
+     */
+    protected AjpMessage requestHeaderMessage = null;
+
+
+    /**
+     * Message used for response header composition.
+     */
+    protected AjpMessage responseHeaderMessage = null;
+
+
+    /**
+     * Body message.
+     */
+    protected AjpMessage bodyMessage = null;
+
+    
+    /**
+     * Body message.
+     */
+    protected MessageBytes bodyBytes = MessageBytes.newInstance();
+
+
+    /**
+     * Error flag.
+     */
+    protected boolean error = false;
+
+
+    /**
+     * Host name (used to avoid useless B2C conversion on the host name).
+     */
+    protected char[] hostNameC = new char[0];
+
+
+    /**
+     * Temp message bytes used for processing.
+     */
+    protected MessageBytes tmpMB = MessageBytes.newInstance();
+
+
+    /**
+     * Byte chunk for certs.
+     */
+    protected MessageBytes certificates = MessageBytes.newInstance();
+
+
+    /**
+     * End of stream flag.
+     */
+    protected boolean endOfStream = false;
+
+
+    /**
+     * Body empty flag.
+     */
+    protected boolean empty = true;
+
+
+    /**
+     * First read.
+     */
+    protected boolean first = true;
+
+
+    /**
+     * Replay read.
+     */
+    protected boolean replay = false;
+
+
+    /**
+     * Finished response.
+     */
+    protected boolean finished = false;
+    
+    
+    /**
+     * Track changes in state for async requests.
+     */
+    protected AsyncStateMachine asyncStateMachine = new AsyncStateMachine(this);
+
+
+    /**
+     * Bytes written to client for the current request
+     */
+    protected long byteCount = 0;
+    
+    
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Use Tomcat authentication ?
+     */
+    protected boolean tomcatAuthentication = true;
+    public boolean getTomcatAuthentication() { return tomcatAuthentication; }
+    public void setTomcatAuthentication(boolean tomcatAuthentication) {
+        this.tomcatAuthentication = tomcatAuthentication;
+    }
+
+
+    /**
+     * Required secret.
+     */
+    protected String requiredSecret = null;
+    public void setRequiredSecret(String requiredSecret) {
+        this.requiredSecret = requiredSecret;
+    }
+
+
+    /**
+     * When client certificate information is presented in a form other than
+     * instances of {@link java.security.cert.X509Certificate} it needs to be
+     * converted before it can be used and this property controls which JSSE
+     * provider is used to perform the conversion. For example it is used with
+     * the AJP connectors, the HTTP APR connector and with the
+     * {@link org.apache.catalina.valves.SSLValve}. If not specified, the
+     * default provider will be used. 
+     */
+    protected String clientCertProvider = null;
+    public String getClientCertProvider() { return clientCertProvider; }
+    public void setClientCertProvider(String s) { this.clientCertProvider = s; }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /** Get the request associated with this processor.
+    *
+    * @return The request
+    */
+   public Request getRequest() {
+       return request;
+   }
+   
+   
+   /**
+    * Send an action to the connector.
+    *
+    * @param actionCode Type of the action
+    * @param param Action parameter
+    */
+   @Override
+   public final void action(ActionCode actionCode, Object param) {
+       
+       if (actionCode == ActionCode.COMMIT) {
+
+           if (response.isCommitted())
+               return;
+
+           // Validate and write response headers
+           try {
+               prepareResponse();
+           } catch (IOException e) {
+               // Set error flag
+               error = true;
+           }
+
+           try {
+               flush(false);
+           } catch (IOException e) {
+               // Set error flag
+               error = true;
+           }
+
+       } else if (actionCode == ActionCode.CLIENT_FLUSH) {
+
+           if (!response.isCommitted()) {
+               // Validate and write response headers
+               try {
+                   prepareResponse();
+               } catch (IOException e) {
+                   // Set error flag
+                   error = true;
+                   return;
+               }
+           }
+
+           try {
+               flush(true);
+           } catch (IOException e) {
+               // Set error flag
+               error = true;
+           }
+
+       } else if (actionCode == ActionCode.DISABLE_SWALLOW_INPUT) {
+           // TODO: Do not swallow request input but
+           // make sure we are closing the connection
+           error = true;
+
+       } else if (actionCode == ActionCode.CLOSE) {
+           // Close
+           // End the processing of the current request, and stop any further
+           // transactions with the client
+
+           try {
+               finish();
+           } catch (IOException e) {
+               // Set error flag
+               error = true;
+           }
+
+       } else if (actionCode == ActionCode.REQ_SSL_ATTRIBUTE ) {
+
+           if (!certificates.isNull()) {
+               ByteChunk certData = certificates.getByteChunk();
+               X509Certificate jsseCerts[] = null;
+               ByteArrayInputStream bais =
+                   new ByteArrayInputStream(certData.getBytes(),
+                           certData.getStart(),
+                           certData.getLength());
+               // Fill the  elements.
+               try {
+                   CertificateFactory cf;
+                   if (clientCertProvider == null) {
+                       cf = CertificateFactory.getInstance("X.509");
+                   } else {
+                       cf = CertificateFactory.getInstance("X.509",
+                               clientCertProvider);
+                   }
+                   while(bais.available() > 0) {
+                       X509Certificate cert = (X509Certificate)
+                           cf.generateCertificate(bais);
+                       if(jsseCerts == null) {
+                           jsseCerts = new X509Certificate[1];
+                           jsseCerts[0] = cert;
+                       } else {
+                           X509Certificate [] temp = new X509Certificate[jsseCerts.length+1];
+                           System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length);
+                           temp[jsseCerts.length] = cert;
+                           jsseCerts = temp;
+                       }
+                   }
+               } catch (java.security.cert.CertificateException e) {
+                   getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
+                   return;
+               } catch (NoSuchProviderException e) {
+                   getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
+                   return;
+               }
+               request.setAttribute(SSLSupport.CERTIFICATE_KEY, jsseCerts);
+           }
+
+       } else if (actionCode == ActionCode.REQ_HOST_ATTRIBUTE) {
+
+           // Get remote host name using a DNS resolution
+           if (request.remoteHost().isNull()) {
+               try {
+                   request.remoteHost().setString(InetAddress.getByName
+                           (request.remoteAddr().toString()).getHostName());
+               } catch (IOException iex) {
+                   // Ignore
+               }
+           }
+
+       } else if (actionCode == ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE) {
+
+           // Copy from local name for now, which should simply be an address
+           request.localAddr().setString(request.localName().toString());
+
+       } else if (actionCode == ActionCode.REQ_SET_BODY_REPLAY) {
+
+           // Set the given bytes as the content
+           ByteChunk bc = (ByteChunk) param;
+           int length = bc.getLength();
+           bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length);
+           request.setContentLength(length);
+           first = false;
+           empty = false;
+           replay = true;
+
+       } else if (actionCode == ActionCode.ASYNC_START) {
+           asyncStateMachine.asyncStart((AsyncContextCallback) param);
+       } else if (actionCode == ActionCode.ASYNC_DISPATCHED) {
+           asyncStateMachine.asyncDispatched();
+       } else if (actionCode == ActionCode.ASYNC_TIMEOUT) {
+           AtomicBoolean result = (AtomicBoolean) param;
+           result.set(asyncStateMachine.asyncTimeout());
+       } else if (actionCode == ActionCode.ASYNC_RUN) {
+           asyncStateMachine.asyncRun((Runnable) param);
+       } else if (actionCode == ActionCode.ASYNC_ERROR) {
+           asyncStateMachine.asyncError();
+       } else if (actionCode == ActionCode.ASYNC_IS_STARTED) {
+           ((AtomicBoolean) param).set(asyncStateMachine.isAsyncStarted());
+       } else if (actionCode == ActionCode.ASYNC_IS_DISPATCHING) {
+           ((AtomicBoolean) param).set(asyncStateMachine.isAsyncDispatching());
+       } else if (actionCode == ActionCode.ASYNC_IS_ASYNC) {
+           ((AtomicBoolean) param).set(asyncStateMachine.isAsync());
+       } else if (actionCode == ActionCode.ASYNC_IS_TIMINGOUT) {
+           ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
+       }  else {
+           actionInternal(actionCode, param);
+       }
+   }
+   
+   // Methods called by action()
+   protected abstract void actionInternal(ActionCode actionCode, Object param);
+   protected abstract void flush(boolean tbd) throws IOException;
+   protected abstract void finish() throws IOException;
+   
+   
+   @Override
+   public abstract Executor getExecutor();
+
+   
+   public void recycle() {
+       asyncStateMachine.recycle();
+
+       // Recycle Request object
+       first = true;
+       endOfStream = false;
+       empty = true;
+       replay = false;
+       finished = false;
+       request.recycle();
+       response.recycle();
+       certificates.recycle();
+       byteCount = 0;
+   }
+   
+   // ------------------------------------------------------ Connector Methods
+
+
+   /**
+    * Set the associated adapter.
+    *
+    * @param adapter the new adapter
+    */
+   public void setAdapter(Adapter adapter) {
+       this.adapter = adapter;
+   }
+
+
+   /**
+    * Get the associated adapter.
+    *
+    * @return the associated adapter
+    */
+   public Adapter getAdapter() {
+       return adapter;
+   }
+   
+   
+   // ------------------------------------------------------ Protected Methods
+
+
+   /**
+    * After reading the request headers, we have to setup the request filters.
+    */
+   protected void prepareRequest() {
+
+       // Translate the HTTP method code to a String.
+       byte methodCode = requestHeaderMessage.getByte();
+       if (methodCode != Constants.SC_M_JK_STORED) {
+           String methodName = Constants.getMethodForCode(methodCode - 1);
+           request.method().setString(methodName);
+       }
+
+       requestHeaderMessage.getBytes(request.protocol());
+       requestHeaderMessage.getBytes(request.requestURI());
+
+       requestHeaderMessage.getBytes(request.remoteAddr());
+       requestHeaderMessage.getBytes(request.remoteHost());
+       requestHeaderMessage.getBytes(request.localName());
+       request.setLocalPort(requestHeaderMessage.getInt());
+
+       boolean isSSL = requestHeaderMessage.getByte() != 0;
+       if (isSSL) {
+           request.scheme().setString("https");
+       }
+
+       // Decode headers
+       MimeHeaders headers = request.getMimeHeaders();
+
+       int hCount = requestHeaderMessage.getInt();
+       for(int i = 0 ; i < hCount ; i++) {
+           String hName = null;
+
+           // Header names are encoded as either an integer code starting
+           // with 0xA0, or as a normal string (in which case the first
+           // two bytes are the length).
+           int isc = requestHeaderMessage.peekInt();
+           int hId = isc & 0xFF;
+
+           MessageBytes vMB = null;
+           isc &= 0xFF00;
+           if(0xA000 == isc) {
+               requestHeaderMessage.getInt(); // To advance the read position
+               hName = Constants.getHeaderForCode(hId - 1);
+               vMB = headers.addValue(hName);
+           } else {
+               // reset hId -- if the header currently being read
+               // happens to be 7 or 8 bytes long, the code below
+               // will think it's the content-type header or the
+               // content-length header - SC_REQ_CONTENT_TYPE=7,
+               // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
+               // behaviour.  see bug 5861 for more information.
+               hId = -1;
+               requestHeaderMessage.getBytes(tmpMB);
+               ByteChunk bc = tmpMB.getByteChunk();
+               vMB = headers.addValue(bc.getBuffer(),
+                       bc.getStart(), bc.getLength());
+           }
+
+           requestHeaderMessage.getBytes(vMB);
+
+           if (hId == Constants.SC_REQ_CONTENT_LENGTH ||
+                   (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
+               // just read the content-length header, so set it
+               long cl = vMB.getLong();
+               if(cl < Integer.MAX_VALUE)
+                   request.setContentLength( (int)cl );
+           } else if (hId == Constants.SC_REQ_CONTENT_TYPE ||
+                   (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
+               // just read the content-type header, so set it
+               ByteChunk bchunk = vMB.getByteChunk();
+               request.contentType().setBytes(bchunk.getBytes(),
+                       bchunk.getOffset(),
+                       bchunk.getLength());
+           }
+       }
+
+       // Decode extra attributes
+       boolean secret = false;
+       byte attributeCode;
+       while ((attributeCode = requestHeaderMessage.getByte())
+               != Constants.SC_A_ARE_DONE) {
+
+           switch (attributeCode) {
+
+           case Constants.SC_A_REQ_ATTRIBUTE :
+               requestHeaderMessage.getBytes(tmpMB);
+               String n = tmpMB.toString();
+               requestHeaderMessage.getBytes(tmpMB);
+               String v = tmpMB.toString();
+               /*
+                * AJP13 misses to forward the remotePort.
+                * Allow the AJP connector to add this info via
+                * a private request attribute.
+                * We will accept the forwarded data as the remote port,
+                * and remove it from the public list of request attributes.
+                */
+               if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
+                   try {
+                       request.setRemotePort(Integer.parseInt(v));
+                   } catch (NumberFormatException nfe) {
+                       // Ignore invalid value
+                   }
+               } else {
+                   request.setAttribute(n, v );
+               }
+               break;
+
+           case Constants.SC_A_CONTEXT :
+               requestHeaderMessage.getBytes(tmpMB);
+               // nothing
+               break;
+
+           case Constants.SC_A_SERVLET_PATH :
+               requestHeaderMessage.getBytes(tmpMB);
+               // nothing
+               break;
+
+           case Constants.SC_A_REMOTE_USER :
+               if (tomcatAuthentication) {
+                   // ignore server
+                   requestHeaderMessage.getBytes(tmpMB);
+               } else {
+                   requestHeaderMessage.getBytes(request.getRemoteUser());
+               }
+               break;
+
+           case Constants.SC_A_AUTH_TYPE :
+               if (tomcatAuthentication) {
+                   // ignore server
+                   requestHeaderMessage.getBytes(tmpMB);
+               } else {
+                   requestHeaderMessage.getBytes(request.getAuthType());
+               }
+               break;
+
+           case Constants.SC_A_QUERY_STRING :
+               requestHeaderMessage.getBytes(request.queryString());
+               break;
+
+           case Constants.SC_A_JVM_ROUTE :
+               requestHeaderMessage.getBytes(request.instanceId());
+               break;
+
+           case Constants.SC_A_SSL_CERT :
+               request.scheme().setString("https");
+               // SSL certificate extraction is lazy, moved to JkCoyoteHandler
+               requestHeaderMessage.getBytes(certificates);
+               break;
+
+           case Constants.SC_A_SSL_CIPHER :
+               request.scheme().setString("https");
+               requestHeaderMessage.getBytes(tmpMB);
+               request.setAttribute(SSLSupport.CIPHER_SUITE_KEY,
+                                    tmpMB.toString());
+               break;
+
+           case Constants.SC_A_SSL_SESSION :
+               request.scheme().setString("https");
+               requestHeaderMessage.getBytes(tmpMB);
+               request.setAttribute(SSLSupport.SESSION_ID_KEY,
+                                    tmpMB.toString());
+               break;
+
+           case Constants.SC_A_SSL_KEY_SIZE :
+               request.setAttribute(SSLSupport.KEY_SIZE_KEY,
+                       Integer.valueOf(requestHeaderMessage.getInt()));
+               break;
+
+           case Constants.SC_A_STORED_METHOD:
+               requestHeaderMessage.getBytes(request.method());
+               break;
+
+           case Constants.SC_A_SECRET:
+               requestHeaderMessage.getBytes(tmpMB);
+               if (requiredSecret != null) {
+                   secret = true;
+                   if (!tmpMB.equals(requiredSecret)) {
+                       response.setStatus(403);
+                       adapter.log(request, response, 0);
+                       error = true;
+                   }
+               }
+               break;
+
+           default:
+               // Ignore unknown attribute for backward compatibility
+               break;
+
+           }
+
+       }
+
+       // Check if secret was submitted if required
+       if ((requiredSecret != null) && !secret) {
+           response.setStatus(403);
+           adapter.log(request, response, 0);
+           error = true;
+       }
+
+       // Check for a full URI (including protocol://host:port/)
+       ByteChunk uriBC = request.requestURI().getByteChunk();
+       if (uriBC.startsWithIgnoreCase("http", 0)) {
+
+           int pos = uriBC.indexOf("://", 0, 3, 4);
+           int uriBCStart = uriBC.getStart();
+           int slashPos = -1;
+           if (pos != -1) {
+               byte[] uriB = uriBC.getBytes();
+               slashPos = uriBC.indexOf('/', pos + 3);
+               if (slashPos == -1) {
+                   slashPos = uriBC.getLength();
+                   // Set URI as "/"
+                   request.requestURI().setBytes
+                       (uriB, uriBCStart + pos + 1, 1);
+               } else {
+                   request.requestURI().setBytes
+                       (uriB, uriBCStart + slashPos,
+                        uriBC.getLength() - slashPos);
+               }
+               MessageBytes hostMB = headers.setValue("host");
+               hostMB.setBytes(uriB, uriBCStart + pos + 3,
+                               slashPos - pos - 3);
+           }
+
+       }
+
+       MessageBytes valueMB = request.getMimeHeaders().getValue("host");
+       parseHost(valueMB);
+
+   }
+   
+   
+   /**
+    * Parse host.
+    */
+   protected void parseHost(MessageBytes valueMB) {
+
+       if (valueMB == null || valueMB.isNull()) {
+           // HTTP/1.0
+           request.setServerPort(request.getLocalPort());
+           try {
+               request.serverName().duplicate(request.localName());
+           } catch (IOException e) {
+               response.setStatus(400);
+               adapter.log(request, response, 0);
+               error = true;
+           }
+           return;
+       }
+
+       ByteChunk valueBC = valueMB.getByteChunk();
+       byte[] valueB = valueBC.getBytes();
+       int valueL = valueBC.getLength();
+       int valueS = valueBC.getStart();
+       int colonPos = -1;
+       if (hostNameC.length < valueL) {
+           hostNameC = new char[valueL];
+       }
+
+       boolean ipv6 = (valueB[valueS] == '[');
+       boolean bracketClosed = false;
+       for (int i = 0; i < valueL; i++) {
+           char b = (char) valueB[i + valueS];
+           hostNameC[i] = b;
+           if (b == ']') {
+               bracketClosed = true;
+           } else if (b == ':') {
+               if (!ipv6 || bracketClosed) {
+                   colonPos = i;
+                   break;
+               }
+           }
+       }
+
+       if (colonPos < 0) {
+           if (request.scheme().equalsIgnoreCase("https")) {
+               // 443 - Default HTTPS port
+               request.setServerPort(443);
+           } else {
+               // 80 - Default HTTTP port
+               request.setServerPort(80);
+           }
+           request.serverName().setChars(hostNameC, 0, valueL);
+       } else {
+
+           request.serverName().setChars(hostNameC, 0, colonPos);
+
+           int port = 0;
+           int mult = 1;
+           for (int i = valueL - 1; i > colonPos; i--) {
+               int charValue = HexUtils.getDec(valueB[i + valueS]);
+               if (charValue == -1) {
+                   // Invalid character
+                   error = true;
+                   // 400 - Bad request
+                   response.setStatus(400);
+                   adapter.log(request, response, 0);
+                   break;
+               }
+               port = port + (charValue * mult);
+               mult = 10 * mult;
+           }
+           request.setServerPort(port);
+       }
+   }
+   
+   
+   /**
+    * When committing the response, we have to validate the set of headers, as
+    * well as setup the response filters.
+    */
+   protected void prepareResponse()
+       throws IOException {
+
+       response.setCommitted(true);
+
+       responseHeaderMessage.reset();
+       responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
+
+       // HTTP header contents
+       responseHeaderMessage.appendInt(response.getStatus());
+       String message = null;
+       if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
+               HttpMessages.isSafeInHttpHeader(response.getMessage())) {
+           message = response.getMessage();
+       }
+       if (message == null){
+           message = HttpMessages.getMessage(response.getStatus());
+       }
+       if (message == null) {
+           // mod_jk + httpd 2.x fails with a null status message - bug 45026
+           message = Integer.toString(response.getStatus());
+       }
+       tmpMB.setString(message);
+       responseHeaderMessage.appendBytes(tmpMB);
+
+       // Special headers
+       MimeHeaders headers = response.getMimeHeaders();
+       String contentType = response.getContentType();
+       if (contentType != null) {
+           headers.setValue("Content-Type").setString(contentType);
+       }
+       String contentLanguage = response.getContentLanguage();
+       if (contentLanguage != null) {
+           headers.setValue("Content-Language").setString(contentLanguage);
+       }
+       long contentLength = response.getContentLengthLong();
+       if (contentLength >= 0) {
+           headers.setValue("Content-Length").setLong(contentLength);
+       }
+
+       // Other headers
+       int numHeaders = headers.size();
+       responseHeaderMessage.appendInt(numHeaders);
+       for (int i = 0; i < numHeaders; i++) {
+           MessageBytes hN = headers.getName(i);
+           int hC = Constants.getResponseAjpIndex(hN.toString());
+           if (hC > 0) {
+               responseHeaderMessage.appendInt(hC);
+           }
+           else {
+               responseHeaderMessage.appendBytes(hN);
+           }
+           MessageBytes hV=headers.getValue(i);
+           responseHeaderMessage.appendBytes(hV);
+       }
+
+       // Write to buffer
+       responseHeaderMessage.end();
+       output(responseHeaderMessage.getBuffer(), 0,
+               responseHeaderMessage.getLen());
+   }
+   
+   // Methods called by prepareResponse()
+   protected abstract void output(byte[] src, int offset, int length)
+           throws IOException;
+   
+   
+   protected boolean isAsync() {
+       return asyncStateMachine.isAsync();
+   }
+   
+   protected SocketState asyncPostProcess() {
+       return asyncStateMachine.asyncPostProcess();
+   }
+
+   // ------------------------------------- InputStreamInputBuffer Inner Class
+
+
+   /**
+    * This class is an input buffer which will read its data from an input
+    * stream.
+    */
+   protected class SocketInputBuffer
+       implements InputBuffer {
+
+
+       /**
+        * Read bytes into the specified chunk.
+        */
+       @Override
+       public int doRead(ByteChunk chunk, Request req )
+           throws IOException {
+
+           if (endOfStream) {
+               return -1;
+           }
+           if (first && req.getContentLengthLong() > 0) {
+               // Handle special first-body-chunk
+               if (!receive()) {
+                   return 0;
+               }
+           } else if (empty) {
+               if (!refillReadBuffer()) {
+                   return -1;
+               }
+           }
+           ByteChunk bc = bodyBytes.getByteChunk();
+           chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength());
+           empty = true;
+           return chunk.getLength();
+
+       }
+
+   }
+   
+   // Methods used by SocketInputBuffer
+   protected abstract boolean receive() throws IOException;
+   protected abstract boolean refillReadBuffer() throws IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AbstractAjpProtocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AbstractAjpProtocol.java
new file mode 100644
index 0000000..3b1cba0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AbstractAjpProtocol.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.ajp;
+
+import org.apache.coyote.AbstractProtocolHandler;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class AbstractAjpProtocol extends AbstractProtocolHandler {
+    
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ------------------------------------------------- AJP specific properties
+    // ------------------------------------------ managed in the ProtocolHandler
+    
+    /**
+     * Should authentication be done in the native webserver layer, 
+     * or in the Servlet container ?
+     */
+    protected boolean tomcatAuthentication = true;
+    public boolean getTomcatAuthentication() { return tomcatAuthentication; }
+    public void setTomcatAuthentication(boolean tomcatAuthentication) {
+        this.tomcatAuthentication = tomcatAuthentication;
+    }
+
+
+    /**
+     * Required secret.
+     */
+    protected String requiredSecret = null;
+    public void setRequiredSecret(String requiredSecret) {
+        this.requiredSecret = requiredSecret;
+    }
+
+
+    /**
+     * AJP packet size.
+     */
+    protected int packetSize = Constants.MAX_PACKET_SIZE;
+    public int getPacketSize() { return packetSize; }
+    public void setPacketSize(int packetSize) {
+        if(packetSize < Constants.MAX_PACKET_SIZE) {
+            this.packetSize = Constants.MAX_PACKET_SIZE;
+        } else {
+            this.packetSize = packetSize;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpAprProcessor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpAprProcessor.java
new file mode 100644
index 0000000..f3b9e8c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpAprProcessor.java
@@ -0,0 +1,732 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.ajp;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executor;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.RequestInfo;
+import org.apache.coyote.Response;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.http.HttpMessages;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Processes AJP requests.
+ *
+ * @author Remy Maucherat
+ * @author Henri Gomez
+ * @author Dan Milstein
+ * @author Keith Wannamaker
+ * @author Kevin Seguin
+ * @author Costin Manolache
+ * @author Bill Barker
+ */
+public class AjpAprProcessor extends AbstractAjpProcessor {
+
+
+    /**
+     * Logger.
+     */
+    private static final Log log = LogFactory.getLog(AjpAprProcessor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public AjpAprProcessor(int packetSize, AprEndpoint endpoint) {
+
+        this.endpoint = endpoint;
+
+        request = new Request();
+        request.setInputBuffer(new SocketInputBuffer());
+
+        response = new Response();
+        response.setHook(this);
+        response.setOutputBuffer(new SocketOutputBuffer());
+        request.setResponse(response);
+
+        this.packetSize = packetSize;
+        requestHeaderMessage = new AjpMessage(packetSize);
+        responseHeaderMessage = new AjpMessage(packetSize);
+        bodyMessage = new AjpMessage(packetSize);
+
+        // Set the get body message buffer
+        AjpMessage getBodyMessage = new AjpMessage(16);
+        getBodyMessage.reset();
+        getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK);
+        // Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
+        getBodyMessage.appendInt(Constants.MAX_READ_SIZE + packetSize - Constants.MAX_PACKET_SIZE);
+        getBodyMessage.end();
+        getBodyMessageBuffer =
+            ByteBuffer.allocateDirect(getBodyMessage.getLen());
+        getBodyMessageBuffer.put(getBodyMessage.getBuffer(), 0,
+                                 getBodyMessage.getLen());
+
+        // Allocate input and output buffers
+        inputBuffer = ByteBuffer.allocateDirect(packetSize * 2);
+        inputBuffer.limit(0);
+        outputBuffer = ByteBuffer.allocateDirect(packetSize * 2);
+
+        // Cause loading of HexUtils
+        HexUtils.load();
+
+        // Cause loading of HttpMessages
+        HttpMessages.getMessage(200);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Socket associated with the current connection.
+     */
+    protected SocketWrapper<Long> socket;
+
+
+    /**
+     * Direct buffer used for input.
+     */
+    protected ByteBuffer inputBuffer = null;
+
+
+    /**
+     * Direct buffer used for output.
+     */
+    protected ByteBuffer outputBuffer = null;
+
+
+    /**
+     * Direct buffer used for sending right away a get body message.
+     */
+    protected final ByteBuffer getBodyMessageBuffer;
+
+
+    /**
+     * Direct buffer used for sending right away a pong message.
+     */
+    protected static final ByteBuffer pongMessageBuffer;
+
+
+    /**
+     * End message array.
+     */
+    protected static final byte[] endMessageArray;
+
+
+    /**
+     * Direct buffer used for sending explicit flush message.
+     */
+    protected static final ByteBuffer flushMessageBuffer;
+
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    static {
+
+        // Set the read body message buffer
+        AjpMessage pongMessage = new AjpMessage(16);
+        pongMessage.reset();
+        pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
+        pongMessage.end();
+        pongMessageBuffer = ByteBuffer.allocateDirect(pongMessage.getLen());
+        pongMessageBuffer.put(pongMessage.getBuffer(), 0,
+                pongMessage.getLen());
+
+        // Allocate the end message array
+        AjpMessage endMessage = new AjpMessage(16);
+        endMessage.reset();
+        endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
+        endMessage.appendByte(1);
+        endMessage.end();
+        endMessageArray = new byte[endMessage.getLen()];
+        System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0,
+                endMessage.getLen());
+
+        // Set the flush message buffer
+        AjpMessage flushMessage = new AjpMessage(16);
+        flushMessage.reset();
+        flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+        flushMessage.appendInt(0);
+        flushMessage.appendByte(0);
+        flushMessage.end();
+        flushMessageBuffer =
+            ByteBuffer.allocateDirect(flushMessage.getLen());
+        flushMessageBuffer.put(flushMessage.getBuffer(), 0,
+                flushMessage.getLen());
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState process(SocketWrapper<Long> socket)
+        throws IOException {
+        RequestInfo rp = request.getRequestProcessor();
+        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+        // Setting up the socket
+        this.socket = socket;
+        long socketRef = socket.getSocket().longValue();
+        Socket.setrbb(socketRef, inputBuffer);
+        Socket.setsbb(socketRef, outputBuffer);
+
+        // Error flag
+        error = false;
+
+        boolean keptAlive = false;
+
+        while (!error && !endpoint.isPaused()) {
+
+            // Parsing the request header
+            try {
+                // Get first message of the request
+                if (!readMessage(requestHeaderMessage, true, keptAlive)) {
+                    // This means that no data is available right now
+                    // (long keepalive), so that the processor should be recycled
+                    // and the method should return true
+                    rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+                    break;
+                }
+                // Check message type, process right away and break if
+                // not regular request processing
+                int type = requestHeaderMessage.getByte();
+                if (type == Constants.JK_AJP13_CPING_REQUEST) {
+                    if (Socket.sendb(socketRef, pongMessageBuffer, 0,
+                            pongMessageBuffer.position()) < 0) {
+                        error = true;
+                    }
+                    continue;
+                } else if(type != Constants.JK_AJP13_FORWARD_REQUEST) {
+                    // Usually the servlet didn't read the previous request body
+                    if(log.isDebugEnabled()) {
+                        log.debug("Unexpected message: "+type);
+                    }
+                    continue;
+                }
+
+                keptAlive = true;
+                request.setStartTime(System.currentTimeMillis());
+            } catch (IOException e) {
+                error = true;
+                break;
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.debug(sm.getString("ajpprocessor.header.error"), t);
+                // 400 - Bad Request
+                response.setStatus(400);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            // Setting up filters, and parse some request headers
+            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+            try {
+                prepareRequest();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.debug(sm.getString("ajpprocessor.request.prepare"), t);
+                // 400 - Internal Server Error
+                response.setStatus(400);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            // Process the request in the adapter
+            if (!error) {
+                try {
+                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+                    adapter.service(request, response);
+                } catch (InterruptedIOException e) {
+                    error = true;
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("ajpprocessor.request.process"), t);
+                    // 500 - Internal Server Error
+                    response.setStatus(500);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+
+            if (isAsync() && !error) {
+                break;
+            }
+
+            // Finish the response if not done yet
+            if (!finished) {
+                try {
+                    finish();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    error = true;
+                }
+            }
+
+            // If there was an error, make sure the request is counted as
+            // and error, and update the statistics counter
+            if (error) {
+                response.setStatus(500);
+            }
+            request.updateCounters();
+
+            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+            recycle();
+        }
+
+        // Add the socket to the poller
+        if (!error && !endpoint.isPaused()) {
+            if (!isAsync()) {
+                ((AprEndpoint)endpoint).getPoller().add(socketRef);
+            }
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+        
+        if (error || endpoint.isPaused()) {
+            return SocketState.CLOSED;
+        } else if (isAsync()) {
+            return SocketState.LONG;
+        } else {
+            return SocketState.OPEN;
+        }
+    }
+
+
+    public SocketState asyncDispatch(SocketWrapper<Long> socket,
+            SocketStatus status) {
+
+        // Setting up the socket
+        this.socket = socket;
+
+        RequestInfo rp = request.getRequestProcessor();
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            error = !adapter.asyncDispatch(request, response, status);
+        } catch (InterruptedIOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (error) {
+            response.setStatus(500);
+        }
+        if (isAsync()) {
+            if (error) {
+                request.updateCounters();
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.LONG;
+            }
+        } else {
+            request.updateCounters();
+            if (error) {
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.OPEN;
+            }
+        }
+        
+    }
+
+    
+    @Override
+    public Executor getExecutor() {
+        return endpoint.getExecutor();
+    }
+    
+    
+    // ----------------------------------------------------- ActionHook Methods
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @Override
+    protected void actionInternal(ActionCode actionCode, Object param) {
+
+        if (actionCode == ActionCode.ASYNC_COMPLETE) {
+            if (asyncStateMachine.asyncComplete()) {
+                ((AprEndpoint)endpoint).processSocketAsync(this.socket,
+                        SocketStatus.OPEN);
+            }
+        } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
+            if (param==null) return;
+            long timeout = ((Long)param).longValue();
+            socket.setTimeout(timeout);
+        } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
+            if (asyncStateMachine.asyncDispatch()) {
+                ((AprEndpoint)endpoint).processSocketAsync(this.socket,
+                        SocketStatus.OPEN);
+            }
+        }
+
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    @Override
+    protected void output(byte[] src, int offset, int length)
+            throws IOException {
+        outputBuffer.put(src, offset, length);
+    }
+
+
+    /**
+     * Finish AJP response.
+     */
+    @Override
+    protected void finish() throws IOException {
+
+        if (!response.isCommitted()) {
+            // Validate and write response headers
+            try {
+                prepareResponse();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+            }
+        }
+
+        if (finished)
+            return;
+
+        finished = true;
+
+        // Add the end message
+        if (outputBuffer.position() + endMessageArray.length > outputBuffer.capacity()) {
+            flush(false);
+        }
+        outputBuffer.put(endMessageArray);
+        flush(false);
+
+    }
+
+
+    /**
+     * Read at least the specified amount of bytes, and place them
+     * in the input buffer.
+     */
+    protected boolean read(int n)
+        throws IOException {
+
+        if (inputBuffer.capacity() - inputBuffer.limit() <=
+                n - inputBuffer.remaining()) {
+            inputBuffer.compact();
+            inputBuffer.limit(inputBuffer.position());
+            inputBuffer.position(0);
+        }
+        int nRead;
+        while (inputBuffer.remaining() < n) {
+            nRead = Socket.recvbb
+                (socket.getSocket().longValue(), inputBuffer.limit(),
+                        inputBuffer.capacity() - inputBuffer.limit());
+            if (nRead > 0) {
+                inputBuffer.limit(inputBuffer.limit() + nRead);
+            } else {
+                throw new IOException(sm.getString("ajpprotocol.failedread"));
+            }
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Read at least the specified amount of bytes, and place them
+     * in the input buffer.
+     */
+    protected boolean readt(int n, boolean useAvailableData)
+        throws IOException {
+
+        if (useAvailableData && inputBuffer.remaining() == 0) {
+            return false;
+        }
+        if (inputBuffer.capacity() - inputBuffer.limit() <=
+                n - inputBuffer.remaining()) {
+            inputBuffer.compact();
+            inputBuffer.limit(inputBuffer.position());
+            inputBuffer.position(0);
+        }
+        int nRead;
+        while (inputBuffer.remaining() < n) {
+            nRead = Socket.recvbb
+                (socket.getSocket().longValue(), inputBuffer.limit(),
+                    inputBuffer.capacity() - inputBuffer.limit());
+            if (nRead > 0) {
+                inputBuffer.limit(inputBuffer.limit() + nRead);
+            } else {
+                if ((-nRead) == Status.ETIMEDOUT || (-nRead) == Status.TIMEUP) {
+                    return false;
+                } else {
+                    throw new IOException(sm.getString("ajpprotocol.failedread"));
+                }
+            }
+        }
+
+        return true;
+
+    }
+
+
+    /** Receive a chunk of data. Called to implement the
+     *  'special' packet in ajp13 and to receive the data
+     *  after we send a GET_BODY packet
+     */
+    @Override
+    public boolean receive() throws IOException {
+
+        first = false;
+        bodyMessage.reset();
+        if (!readMessage(bodyMessage, false, false)) {
+            // Invalid message
+            return false;
+        }
+        // No data received.
+        if (bodyMessage.getLen() == 0) {
+            // just the header
+            // Don't mark 'end of stream' for the first chunk.
+            return false;
+        }
+        int blen = bodyMessage.peekInt();
+        if (blen == 0) {
+            return false;
+        }
+
+        bodyMessage.getBytes(bodyBytes);
+        empty = false;
+        return true;
+    }
+
+    /**
+     * Get more request body data from the web server and store it in the
+     * internal buffer.
+     *
+     * @return true if there is more data, false if not.
+     */
+    @Override
+    protected boolean refillReadBuffer() throws IOException {
+        // If the server returns an empty packet, assume that that end of
+        // the stream has been reached (yuck -- fix protocol??).
+        // FORM support
+        if (replay) {
+            endOfStream = true; // we've read everything there is
+        }
+        if (endOfStream) {
+            return false;
+        }
+
+        // Request more data immediately
+        Socket.sendb(socket.getSocket().longValue(), getBodyMessageBuffer, 0,
+                getBodyMessageBuffer.position());
+
+        boolean moreData = receive();
+        if( !moreData ) {
+            endOfStream = true;
+        }
+        return moreData;
+    }
+
+
+    /**
+     * Read an AJP message.
+     *
+     * @param first is true if the message is the first in the request, which
+     *        will cause a short duration blocking read
+     * @return true if the message has been read, false if the short read
+     *         didn't return anything
+     * @throws IOException any other failure, including incomplete reads
+     */
+    protected boolean readMessage(AjpMessage message, boolean first,
+            boolean useAvailableData)
+        throws IOException {
+
+        int headerLength = message.getHeaderLength();
+
+        if (first) {
+            if (!readt(headerLength, useAvailableData)) {
+                return false;
+            }
+        } else {
+            read(headerLength);
+        }
+        inputBuffer.get(message.getBuffer(), 0, headerLength);
+        int messageLength = message.processHeader();
+        if (messageLength < 0) {
+            // Invalid AJP header signature
+            // TODO: Throw some exception and close the connection to frontend.
+            return false;
+        }
+        else if (messageLength == 0) {
+            // Zero length message.
+            return true;
+        }
+        else {
+            read(messageLength);
+            inputBuffer.get(message.getBuffer(), headerLength, messageLength);
+            return true;
+        }
+
+    }
+
+
+    /**
+     * Recycle the processor.
+     */
+    @Override
+    public void recycle() {
+        super.recycle();
+
+        inputBuffer.clear();
+        inputBuffer.limit(0);
+        outputBuffer.clear();
+
+    }
+
+
+    /**
+     * Callback to write data from the buffer.
+     */
+    @Override
+    protected void flush(boolean explicit) throws IOException {
+        
+        long socketRef = socket.getSocket().longValue();
+        
+        if (outputBuffer.position() > 0) {
+            if (Socket.sendbb(socketRef, 0, outputBuffer.position()) < 0) {
+                throw new IOException(sm.getString("ajpprocessor.failedsend"));
+            }
+            outputBuffer.clear();
+        }
+        // Send explicit flush message
+        if (explicit && !finished) {
+            if (Socket.sendb(socketRef, flushMessageBuffer, 0,
+                    flushMessageBuffer.position()) < 0) {
+                throw new IOException(sm.getString("ajpprocessor.failedflush"));
+            }
+        }
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer
+        implements OutputBuffer {
+
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res)
+            throws IOException {
+
+            if (!response.isCommitted()) {
+                // Validate and write response headers
+                try {
+                    prepareResponse();
+                } catch (IOException e) {
+                    // Set error flag
+                    error = true;
+                }
+            }
+
+            int len = chunk.getLength();
+            // 4 - hardcoded, byte[] marshaling overhead
+            // Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
+            int chunkSize = Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE;
+            int off = 0;
+            while (len > 0) {
+                int thisTime = len;
+                if (thisTime > chunkSize) {
+                    thisTime = chunkSize;
+                }
+                len -= thisTime;
+                if (outputBuffer.position() + thisTime +
+                    Constants.H_SIZE + 4 > outputBuffer.capacity()) {
+                    flush(false);
+                }
+                outputBuffer.put((byte) 0x41);
+                outputBuffer.put((byte) 0x42);
+                outputBuffer.putShort((short) (thisTime + 4));
+                outputBuffer.put(Constants.JK_AJP13_SEND_BODY_CHUNK);
+                outputBuffer.putShort((short) thisTime);
+                outputBuffer.put(chunk.getBytes(), chunk.getOffset() + off, thisTime);
+                outputBuffer.put((byte) 0x00);
+                off += thisTime;
+            }
+
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpAprProtocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpAprProtocol.java
new file mode 100644
index 0000000..68facef
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpAprProtocol.java
@@ -0,0 +1,320 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.ajp;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.ObjectName;
+
+import org.apache.coyote.RequestGroupInfo;
+import org.apache.coyote.RequestInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.AprEndpoint.Handler;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class AjpAprProtocol extends AbstractAjpProtocol {
+    
+    
+    private static final Log log = LogFactory.getLog(AjpAprProtocol.class);
+
+    @Override
+    protected Log getLog() { return log; }
+
+
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    public AjpAprProtocol() {
+        endpoint = new AprEndpoint();
+        cHandler = new AjpConnectionHandler(this);
+        ((AprEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+        setUseSendfile(Constants.DEFAULT_USE_SENDFILE);
+    }
+
+    
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Connection handler for AJP.
+     */
+    private AjpConnectionHandler cHandler;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    public boolean getUseSendfile() { return endpoint.getUseSendfile(); }
+    public void setUseSendfile(@SuppressWarnings("unused") boolean useSendfile) {
+        /* No sendfile for AJP */
+    }
+
+    public int getPollTime() { return ((AprEndpoint)endpoint).getPollTime(); }
+    public void setPollTime(int pollTime) { ((AprEndpoint)endpoint).setPollTime(pollTime); }
+
+    public void setPollerSize(int pollerSize) { ((AprEndpoint)endpoint).setPollerSize(pollerSize); }
+    public int getPollerSize() { return ((AprEndpoint)endpoint).getPollerSize(); }
+
+
+    // ----------------------------------------------------- JMX related methods
+
+    @Override
+    protected String getNamePrefix() {
+        return ("ajp-apr");
+    }
+
+
+    // --------------------------------------  AjpConnectionHandler Inner Class
+
+
+    protected static class AjpConnectionHandler implements Handler {
+
+        protected AjpAprProtocol proto;
+        protected AtomicLong registerCount = new AtomicLong(0);
+        protected RequestGroupInfo global = new RequestGroupInfo();
+
+        protected ConcurrentHashMap<SocketWrapper<Long>, AjpAprProcessor> connections =
+            new ConcurrentHashMap<SocketWrapper<Long>, AjpAprProcessor>();
+
+        protected ConcurrentLinkedQueue<AjpAprProcessor> recycledProcessors = 
+            new ConcurrentLinkedQueue<AjpAprProcessor>() {
+            private static final long serialVersionUID = 1L;
+            protected AtomicInteger size = new AtomicInteger(0);
+            @Override
+            public boolean offer(AjpAprProcessor processor) {
+                boolean offer = (proto.processorCache == -1) ? true : (size.get() < proto.processorCache);
+                //avoid over growing our cache or add after we have stopped
+                boolean result = false;
+                if ( offer ) {
+                    result = super.offer(processor);
+                    if ( result ) {
+                        size.incrementAndGet();
+                    }
+                }
+                if (!result) unregister(processor);
+                return result;
+            }
+            
+            @Override
+            public AjpAprProcessor poll() {
+                AjpAprProcessor result = super.poll();
+                if ( result != null ) {
+                    size.decrementAndGet();
+                }
+                return result;
+            }
+            
+            @Override
+            public void clear() {
+                AjpAprProcessor next = poll();
+                while ( next != null ) {
+                    unregister(next);
+                    next = poll();
+                }
+                super.clear();
+                size.set(0);
+            }
+        };
+
+        public AjpConnectionHandler(AjpAprProtocol proto) {
+            this.proto = proto;
+        }
+
+        @Override
+        public Object getGlobal() {
+            return global;
+        }
+
+        @Override
+        public void recycle() {
+            recycledProcessors.clear();
+        }
+        
+        // FIXME: Support for this could be added in AJP as well
+        @Override
+        public SocketState event(SocketWrapper<Long> socket, SocketStatus status) {
+            return SocketState.CLOSED;
+        }
+        
+        @Override
+        public SocketState process(SocketWrapper<Long> socket) {
+            AjpAprProcessor processor = recycledProcessors.poll();
+            try {
+                if (processor == null) {
+                    processor = createProcessor();
+                }
+
+                SocketState state = processor.process(socket);
+                if (state == SocketState.LONG) {
+                    // Check if the post processing is going to change the state
+                    state = processor.asyncPostProcess();
+                }
+                if (state == SocketState.LONG || state == SocketState.ASYNC_END) {
+                    // Need to make socket available for next processing cycle
+                    // but no need for the poller
+                    connections.put(socket, processor);
+                    socket.setAsync(true);
+                } else {
+                    processor.recycle();
+                    recycledProcessors.offer(processor);
+                }
+                return state;
+
+            } catch(java.net.SocketException e) {
+                // SocketExceptions are normal
+                log.debug(sm.getString(
+                        "ajpprotocol.proto.socketexception.debug"), e);
+            } catch (java.io.IOException e) {
+                // IOExceptions are normal
+                log.debug(sm.getString(
+                        "ajpprotocol.proto.ioexception.debug"), e);
+            }
+            // Future developers: if you discover any other
+            // rare-but-nonfatal exceptions, catch them here, and log as
+            // above.
+            catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                // any other exception or error is odd. Here we log it
+                // with "ERROR" level, so it will show up even on
+                // less-than-verbose logs.
+                log.error(sm.getString("ajpprotocol.proto.error"), e);
+            }
+            processor.recycle();
+            recycledProcessors.offer(processor);
+            return SocketState.CLOSED;
+        }
+
+        @Override
+        public SocketState asyncDispatch(SocketWrapper<Long> socket, SocketStatus status) {
+
+            AjpAprProcessor processor = connections.get(socket);
+            
+            SocketState state = SocketState.CLOSED; 
+            if (processor != null) {
+                // Call the appropriate event
+                try {
+                    state = processor.asyncDispatch(socket, status);
+                }
+                // Future developers: if you discover any other
+                // rare-but-nonfatal exceptions, catch them here, and log as
+                // debug.
+                catch (Throwable e) {
+                    ExceptionUtils.handleThrowable(e);
+                    // any other exception or error is odd. Here we log it
+                    // with "ERROR" level, so it will show up even on
+                    // less-than-verbose logs.
+                    AjpAprProtocol.log.error
+                        (sm.getString("ajpprotocol.proto.error"), e);
+                } finally {
+                    if (state == SocketState.LONG && processor.isAsync()) {
+                        state = processor.asyncPostProcess();
+                    }
+                    if (state != SocketState.LONG && state != SocketState.ASYNC_END) {
+                        connections.remove(socket);
+                        processor.recycle();
+                        recycledProcessors.offer(processor);
+                        if (state == SocketState.OPEN) {
+                            ((AprEndpoint)proto.endpoint).getPoller().add(socket.getSocket().longValue());
+                        }
+                    }
+                }
+            }
+            return state;
+        }
+        
+        protected AjpAprProcessor createProcessor() {
+            AjpAprProcessor processor = new AjpAprProcessor(proto.packetSize, (AprEndpoint)proto.endpoint);
+            processor.setAdapter(proto.adapter);
+            processor.setTomcatAuthentication(proto.tomcatAuthentication);
+            processor.setRequiredSecret(proto.requiredSecret);
+            processor.setClientCertProvider(proto.getClientCertProvider());
+            register(processor);
+            return processor;
+        }
+        
+        protected void register(AjpAprProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        long count = registerCount.incrementAndGet();
+                        RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(global);
+                        ObjectName rpName = new ObjectName
+                            (proto.getDomain() + ":type=RequestProcessor,worker="
+                                + proto.getName() + ",name=AjpRequest" + count);
+                        if (log.isDebugEnabled()) {
+                            log.debug("Register " + rpName);
+                        }
+                        Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                        rp.setRpName(rpName);
+                    } catch (Exception e) {
+                        log.warn("Error registering request");
+                    }
+                }
+            }
+        }
+
+        protected void unregister(AjpAprProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(null);
+                        ObjectName rpName = rp.getRpName();
+                        if (log.isDebugEnabled()) {
+                            log.debug("Unregister " + rpName);
+                        }
+                        Registry.getRegistry(null, null).unregisterComponent(rpName);
+                        rp.setRpName(null);
+                    } catch (Exception e) {
+                        log.warn("Error unregistering request", e);
+                    }
+                }
+            }
+        }
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpMessage.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpMessage.java
new file mode 100644
index 0000000..491e280
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpMessage.java
@@ -0,0 +1,424 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.ajp;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * A single packet for communication between the web server and the
+ * container.  Designed to be reused many times with no creation of
+ * garbage.  Understands the format of data types for these packets.
+ * Can be used (somewhat confusingly) for both incoming and outgoing
+ * packets.  
+ *
+ * @author Henri Gomez
+ * @author Dan Milstein
+ * @author Keith Wannamaker
+ * @author Kevin Seguin
+ * @author Costin Manolache
+ */
+public class AjpMessage {
+
+
+    private static final Log log = LogFactory.getLog(AjpMessage.class);
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ------------------------------------------------------------ Constructor
+
+    
+    public AjpMessage(int packetSize) {
+        buf = new byte[packetSize];
+    }
+    
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Fixed size buffer.
+     */
+    protected byte buf[] = null;
+
+
+    /**
+     * The current read or write position in the buffer.
+     */
+    protected int pos;
+
+
+    /**
+     * This actually means different things depending on whether the
+     * packet is read or write.  For read, it's the length of the
+     * payload (excluding the header).  For write, it's the length of
+     * the packet as a whole (counting the header).  Oh, well.
+     */
+    protected int len; 
+
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Prepare this packet for accumulating a message from the container to
+     * the web server.  Set the write position to just after the header
+     * (but leave the length unwritten, because it is as yet unknown).
+     */
+    public void reset() {
+        len = 4;
+        pos = 4;
+    }
+
+
+    /**
+     * For a packet to be sent to the web server, finish the process of
+     * accumulating data and write the length of the data payload into
+     * the header.  
+     */
+    public void end() {
+        len = pos;
+        int dLen = len - 4;
+
+        buf[0] = (byte) 0x41;
+        buf[1] = (byte) 0x42;
+        buf[2] = (byte) ((dLen>>>8) & 0xFF);
+        buf[3] = (byte) (dLen & 0xFF);
+    }
+
+
+    /**
+     * Return the underlying byte buffer.
+     */
+    public byte[] getBuffer() {
+        return buf;
+    }
+
+
+    /**
+     * Return the current message length. For read, it's the length of the
+     * payload (excluding the header).  For write, it's the length of
+     * the packet as a whole (counting the header).
+     */
+    public int getLen() {
+        return len;
+    }
+    
+
+    /**
+     * Add a short integer (2 bytes) to the message.
+     */
+    public void appendInt(int val) {
+        buf[pos++] = (byte) ((val >>> 8) & 0xFF);
+        buf[pos++] = (byte) (val & 0xFF);
+    }
+
+
+    /**
+     * Append a byte (1 byte) to the message.
+     */
+    public void appendByte(int val) {
+        buf[pos++] = (byte) val;
+    }
+
+    
+    /**
+     * Write a MessageBytes out at the current write position.
+     * A null MessageBytes is encoded as a string with length 0.  
+     */
+    public void appendBytes(MessageBytes mb) {
+        if (mb == null) {
+            log.error(sm.getString("ajpmessage.null"), 
+                    new NullPointerException());
+            appendInt(0);
+            appendByte(0);
+            return;
+        }
+        if (mb.getType() == MessageBytes.T_BYTES) {
+            ByteChunk bc = mb.getByteChunk();
+            appendByteChunk(bc);
+        } else if (mb.getType() == MessageBytes.T_CHARS) {
+            CharChunk cc = mb.getCharChunk();
+            appendCharChunk(cc);
+        } else {
+            appendString(mb.toString());
+        }
+    }
+
+    
+    /**
+     * Write a ByteChunk out at the current write position.
+     * A null ByteChunk is encoded as a string with length 0.  
+     */
+    public void appendByteChunk(ByteChunk bc) {
+        if (bc == null) {
+            log.error(sm.getString("ajpmessage.null"), 
+                    new NullPointerException());
+            appendInt(0);
+            appendByte(0);
+            return;
+        }
+        appendBytes(bc.getBytes(), bc.getStart(), bc.getLength());
+    }
+
+    
+    /**
+     * Write a CharChunk out at the current write position.
+     * A null CharChunk is encoded as a string with length 0.  
+     */
+    public void appendCharChunk(CharChunk cc) {
+        if (cc == null) {
+            log.error(sm.getString("ajpmessage.null"), 
+                    new NullPointerException());
+            appendInt(0);
+            appendByte(0);
+            return;
+        }
+        int start = cc.getStart();
+        int end = cc.getEnd();
+        appendInt(end - start);
+        char[] cbuf = cc.getBuffer();
+        for (int i = start; i < end; i++) {
+            char c = cbuf[i];
+            // Note:  This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework.  It must suffice until servlet output
+            // streams properly encode their output.
+            if ((c <= 31) && (c != 9)) {
+                c = ' ';
+            } else if (c == 127) {
+                c = ' ';
+            }
+            appendByte(c);
+        }
+        appendByte(0);
+    }
+
+    
+    /**
+     * Write a String out at the current write position.  Strings are
+     * encoded with the length in two bytes first, then the string, and
+     * then a terminating \0 (which is <B>not</B> included in the
+     * encoded length).  The terminator is for the convenience of the C
+     * code, where it saves a round of copying.  A null string is
+     * encoded as a string with length 0.  
+     */
+    public void appendString(String str) {
+        if (str == null) {
+            log.error(sm.getString("ajpmessage.null"), 
+                    new NullPointerException());
+            appendInt(0);
+            appendByte(0);
+            return;
+        }
+        int len = str.length();
+        appendInt(len);
+        for (int i = 0; i < len; i++) {
+            char c = str.charAt (i);
+            // Note:  This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework.  It must suffice until servlet output
+            // streams properly encode their output.
+            if ((c <= 31) && (c != 9)) {
+                c = ' ';
+            } else if (c == 127) {
+                c = ' ';
+            }
+            appendByte(c);
+        }
+        appendByte(0);
+    }
+
+    
+    /** 
+     * Copy a chunk of bytes into the packet, starting at the current
+     * write position.  The chunk of bytes is encoded with the length
+     * in two bytes first, then the data itself, and finally a
+     * terminating \0 (which is <B>not</B> included in the encoded
+     * length).
+     *
+     * @param b The array from which to copy bytes.
+     * @param off The offset into the array at which to start copying
+     * @param numBytes The number of bytes to copy.  
+     */
+    public void appendBytes(byte[] b, int off, int numBytes) {
+        if (pos + numBytes + 3 > buf.length) {
+            log.error(sm.getString("ajpmessage.overflow", "" + numBytes, "" + pos),
+                    new ArrayIndexOutOfBoundsException());
+            if (log.isDebugEnabled()) {
+                dump("Overflow/coBytes");
+            }
+            return;
+        }
+        appendInt(numBytes);
+        System.arraycopy(b, off, buf, pos, numBytes);
+        pos += numBytes;
+        appendByte(0);
+    }
+
+    
+    /**
+     * Read an integer from packet, and advance the read position past
+     * it.  Integers are encoded as two unsigned bytes with the
+     * high-order byte first, and, as far as I can tell, in
+     * little-endian order within each byte.  
+     */
+    public int getInt() {
+        int b1 = buf[pos++] & 0xFF;
+        int b2 = buf[pos++] & 0xFF;
+        return (b1<<8) + b2;
+    }
+
+
+    public int peekInt() {
+        int b1 = buf[pos] & 0xFF;
+        int b2 = buf[pos+1] & 0xFF;
+        return (b1<<8) + b2;
+    }
+
+    
+    public byte getByte() {
+        byte res = buf[pos++];
+        return res;
+    }
+
+    
+    public void getBytes(MessageBytes mb) {
+        int length = getInt();
+        if ((length == 0xFFFF) || (length == -1)) {
+            mb.recycle();
+            return;
+        }
+        mb.setBytes(buf, pos, length);
+        mb.getCharChunk().recycle(); // not valid anymore
+        pos += length;
+        pos++; // Skip the terminating \0
+    }
+    
+    
+    /**
+     * Read a 32 bits integer from packet, and advance the read position past
+     * it.  Integers are encoded as four unsigned bytes with the
+     * high-order byte first, and, as far as I can tell, in
+     * little-endian order within each byte.
+     */
+    public int getLongInt() {
+        int b1 = buf[pos++] & 0xFF; // No swap, Java order
+        b1 <<= 8;
+        b1 |= (buf[pos++] & 0xFF);
+        b1 <<= 8;
+        b1 |= (buf[pos++] & 0xFF);
+        b1 <<=8;
+        b1 |= (buf[pos++] & 0xFF);
+        return  b1;
+    }
+
+
+    public int getHeaderLength() {
+        return 4;
+    }
+
+    
+    public int getPacketSize() {
+        return buf.length;
+    }
+    
+    
+    public int processHeader() {
+        pos = 0;
+        int mark = getInt();
+        len = getInt();
+        // Verify message signature
+        if ((mark != 0x1234) && (mark != 0x4142)) {
+            log.error(sm.getString("ajpmessage.invalid", "" + mark));
+            if (log.isDebugEnabled()) {
+                dump("In: ");
+            }
+            return -1;
+        }
+        if (log.isDebugEnabled())  {
+            log.debug("Received " + len + " " + buf[0]);
+        }
+        return len;
+    }
+    
+
+    /**
+     * Dump the contents of the message, prefixed with the given String.
+     */
+    public void dump(String msg) {
+        if (log.isDebugEnabled()) {
+            log.debug(msg + ": " + HexUtils.toHexString(buf) + " " + pos +"/" + (len + 4));
+        }
+        int max = pos;
+        if (len + 4 > pos)
+            max = len+4;
+        if (max > 1000)
+            max = 1000;
+        if (log.isDebugEnabled()) {
+            for (int j = 0; j < max; j += 16) { 
+                log.debug(hexLine(buf, j, len));
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    protected static String hexLine(byte buf[], int start, int len) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = start; i < start + 16 ; i++) {
+            if (i < len + 4) {
+                sb.append(hex(buf[i]) + " ");
+            } else { 
+                sb.append("   ");
+            }
+        }
+        sb.append(" | ");
+        for (int i = start; i < start + 16 && i < len + 4; i++) {
+            if (!Character.isISOControl((char) buf[i])) {
+                sb.append(Character.valueOf((char) buf[i]));
+            } else {
+                sb.append(".");
+            }
+        }
+        return sb.toString();
+    }
+
+
+    protected static String hex(int x) {
+        String h = Integer.toHexString(x);
+        if (h.length() == 1) {
+            h = "0" + h;
+        }
+        return h.substring(h.length() - 2);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpProcessor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpProcessor.java
new file mode 100644
index 0000000..c4023d2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpProcessor.java
@@ -0,0 +1,646 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.ajp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.concurrent.Executor;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.RequestInfo;
+import org.apache.coyote.Response;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.http.HttpMessages;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Processes AJP requests.
+ *
+ * @author Remy Maucherat
+ * @author Henri Gomez
+ * @author Dan Milstein
+ * @author Keith Wannamaker
+ * @author Kevin Seguin
+ * @author Costin Manolache
+ * @author Bill Barker
+ */
+public class AjpProcessor extends AbstractAjpProcessor {
+
+
+    /**
+     * Logger.
+     */
+    private static final Log log = LogFactory.getLog(AjpProcessor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public AjpProcessor(int packetSize, JIoEndpoint endpoint) {
+
+        this.endpoint = endpoint;
+
+        request = new Request();
+        request.setInputBuffer(new SocketInputBuffer());
+
+        response = new Response();
+        response.setHook(this);
+        response.setOutputBuffer(new SocketOutputBuffer());
+        request.setResponse(response);
+
+        this.packetSize = packetSize;
+        requestHeaderMessage = new AjpMessage(packetSize);
+        responseHeaderMessage = new AjpMessage(packetSize);
+        bodyMessage = new AjpMessage(packetSize);
+
+        // Set the get body message buffer
+        AjpMessage getBodyMessage = new AjpMessage(16);
+        getBodyMessage.reset();
+        getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK);
+        // Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
+        getBodyMessage.appendInt(Constants.MAX_READ_SIZE + packetSize - Constants.MAX_PACKET_SIZE);
+        getBodyMessage.end();
+        getBodyMessageArray = new byte[getBodyMessage.getLen()];
+        System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray, 
+                         0, getBodyMessage.getLen());
+
+        // Cause loading of HexUtils
+        HexUtils.load();
+
+        // Cause loading of HttpMessages
+        HttpMessages.getMessage(200);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Socket associated with the current connection.
+     */
+    protected SocketWrapper<Socket> socket;
+
+    
+    /**
+     * Input stream.
+     */
+    protected InputStream input;
+    
+    
+    /**
+     * Output stream.
+     */
+    protected OutputStream output;
+    
+
+    /**
+     * Direct buffer used for sending right away a get body message.
+     */
+    protected final byte[] getBodyMessageArray;
+
+
+    /**
+     * Direct buffer used for sending right away a pong message.
+     */
+    protected static final byte[] pongMessageArray;
+
+
+    /**
+     * End message array.
+     */
+    protected static final byte[] endMessageArray;
+
+
+    /**
+     * Flush message array.
+     */
+    protected static final byte[] flushMessageArray;
+    
+    // ----------------------------------------------------- Static Initializer
+
+
+    static {
+
+        // Set the read body message buffer
+        AjpMessage pongMessage = new AjpMessage(16);
+        pongMessage.reset();
+        pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
+        pongMessage.end();
+        pongMessageArray = new byte[pongMessage.getLen()];
+        System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray, 
+                0, pongMessage.getLen());
+
+        // Allocate the end message array
+        AjpMessage endMessage = new AjpMessage(16);
+        endMessage.reset();
+        endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
+        endMessage.appendByte(1);
+        endMessage.end();
+        endMessageArray = new byte[endMessage.getLen()];
+        System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0,
+                endMessage.getLen());
+
+        // Allocate the flush message array
+        AjpMessage flushMessage = new AjpMessage(16);
+        flushMessage.reset();
+        flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+        flushMessage.appendInt(0);
+        flushMessage.appendByte(0);
+        flushMessage.end();
+        flushMessageArray = new byte[flushMessage.getLen()];
+        System.arraycopy(flushMessage.getBuffer(), 0, flushMessageArray, 0,
+                flushMessage.getLen());
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The number of milliseconds Tomcat will wait for a subsequent request
+     * before closing the connection. The default is the same as for
+     * Apache HTTP Server (15 000 milliseconds).
+     */
+    protected int keepAliveTimeout = -1;
+    public int getKeepAliveTimeout() { return keepAliveTimeout; }
+    public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState process(SocketWrapper<Socket> socket)
+        throws IOException {
+        RequestInfo rp = request.getRequestProcessor();
+        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+        // Setting up the socket
+        this.socket = socket;
+        input = socket.getSocket().getInputStream();
+        output = socket.getSocket().getOutputStream();
+        int soTimeout = -1;
+        if (keepAliveTimeout > 0) {
+            soTimeout = socket.getSocket().getSoTimeout();
+        }
+
+        // Error flag
+        error = false;
+
+        while (!error && !endpoint.isPaused()) {
+
+            // Parsing the request header
+            try {
+                // Set keep alive timeout if enabled
+                if (keepAliveTimeout > 0) {
+                    socket.getSocket().setSoTimeout(keepAliveTimeout);
+                }
+                // Get first message of the request
+                if (!readMessage(requestHeaderMessage)) {
+                    // This means a connection timeout
+                    rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+                    break;
+                }
+                // Set back timeout if keep alive timeout is enabled
+                if (keepAliveTimeout > 0) {
+                    socket.getSocket().setSoTimeout(soTimeout);
+                }
+                // Check message type, process right away and break if
+                // not regular request processing
+                int type = requestHeaderMessage.getByte();
+                if (type == Constants.JK_AJP13_CPING_REQUEST) {
+                    try {
+                        output.write(pongMessageArray);
+                    } catch (IOException e) {
+                        error = true;
+                    }
+                    continue;
+                } else if(type != Constants.JK_AJP13_FORWARD_REQUEST) {
+                    // Usually the servlet didn't read the previous request body
+                    if(log.isDebugEnabled()) {
+                        log.debug("Unexpected message: "+type);
+                    }
+                    continue;
+                }
+
+                request.setStartTime(System.currentTimeMillis());
+            } catch (IOException e) {
+                error = true;
+                break;
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.debug(sm.getString("ajpprocessor.header.error"), t);
+                // 400 - Bad Request
+                response.setStatus(400);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            // Setting up filters, and parse some request headers
+            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+            try {
+                prepareRequest();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.debug(sm.getString("ajpprocessor.request.prepare"), t);
+                // 400 - Internal Server Error
+                response.setStatus(400);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            if (endpoint.isPaused()) {
+                // 503 - Service unavailable
+                response.setStatus(503);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            // Process the request in the adapter
+            if (!error) {
+                try {
+                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+                    adapter.service(request, response);
+                } catch (InterruptedIOException e) {
+                    error = true;
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("ajpprocessor.request.process"), t);
+                    // 500 - Internal Server Error
+                    response.setStatus(500);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+            
+            if (isAsync() && !error) {
+                break;
+            }
+
+            // Finish the response if not done yet
+            if (!finished) {
+                try {
+                    finish();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    error = true;
+                }
+            }
+
+            // If there was an error, make sure the request is counted as
+            // and error, and update the statistics counter
+            if (error) {
+                response.setStatus(500);
+            }
+            request.updateCounters();
+
+            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+            recycle();
+        }
+        
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (isAsync() && !error && !endpoint.isPaused()) {
+            return SocketState.LONG;
+        } else {
+            input = null;
+            output = null;
+            return SocketState.CLOSED;
+        }
+        
+    }
+
+    public SocketState asyncDispatch(SocketStatus status) {
+
+        RequestInfo rp = request.getRequestProcessor();
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            error = !adapter.asyncDispatch(request, response, status);
+        } catch (InterruptedIOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (isAsync()) {
+            if (error) {
+                response.setStatus(500);
+                request.updateCounters();
+                input = null;
+                output = null;
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.LONG;
+            }
+        } else {
+            if (error) {
+                response.setStatus(500);
+            }
+            request.updateCounters();
+            input = null;
+            output = null;
+            return SocketState.CLOSED;
+        }
+
+
+    }
+
+    
+    @Override
+    public Executor getExecutor() {
+        return endpoint.getExecutor();
+    }
+    
+    
+    // ----------------------------------------------------- ActionHook Methods
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @Override
+    protected void actionInternal(ActionCode actionCode, Object param) {
+
+        if (actionCode == ActionCode.ASYNC_COMPLETE) {
+            if (asyncStateMachine.asyncComplete()) {
+                ((JIoEndpoint)endpoint).processSocketAsync(this.socket,
+                        SocketStatus.OPEN);
+            }
+        } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
+            if (param == null) return;
+            long timeout = ((Long)param).longValue();
+            // if we are not piggy backing on a worker thread, set the timeout
+            socket.setTimeout(timeout);
+        } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
+            if (asyncStateMachine.asyncDispatch()) {
+                ((JIoEndpoint)endpoint).processSocketAsync(this.socket,
+                        SocketStatus.OPEN);
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+    @Override
+    protected void output(byte[] src, int offset, int length)
+            throws IOException {
+        output.write(src, offset, length);
+    }
+
+    /**
+     * Finish AJP response.
+     */
+    @Override
+    protected void finish() throws IOException {
+
+        if (!response.isCommitted()) {
+            // Validate and write response headers
+            try {
+                prepareResponse();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+            }
+        }
+
+        if (finished)
+            return;
+
+        finished = true;
+
+        // Add the end message
+        output.write(endMessageArray);
+
+    }
+
+
+    /**
+     * Read at least the specified amount of bytes, and place them
+     * in the input buffer.
+     */
+    protected boolean read(byte[] buf, int pos, int n)
+        throws IOException {
+
+        int read = 0;
+        int res = 0;
+        while (read < n) {
+            res = input.read(buf, read + pos, n - read);
+            if (res > 0) {
+                read += res;
+            } else {
+                throw new IOException(sm.getString("ajpprotocol.failedread"));
+            }
+        }
+        
+        return true;
+
+    }
+
+
+    /** Receive a chunk of data. Called to implement the
+     *  'special' packet in ajp13 and to receive the data
+     *  after we send a GET_BODY packet
+     */
+    @Override
+    public boolean receive() throws IOException {
+
+        first = false;
+        bodyMessage.reset();
+        if (!readMessage(bodyMessage)) {
+            // Invalid message
+            return false;
+        }
+        // No data received.
+        if (bodyMessage.getLen() == 0) {
+            // just the header
+            // Don't mark 'end of stream' for the first chunk.
+            return false;
+        }
+        int blen = bodyMessage.peekInt();
+        if (blen == 0) {
+            return false;
+        }
+
+        bodyMessage.getBytes(bodyBytes);
+        empty = false;
+        return true;
+    }
+
+    /**
+     * Get more request body data from the web server and store it in the
+     * internal buffer.
+     *
+     * @return true if there is more data, false if not.
+     */
+    @Override
+    protected boolean refillReadBuffer() throws IOException {
+        // If the server returns an empty packet, assume that that end of
+        // the stream has been reached (yuck -- fix protocol??).
+        // FORM support
+        if (replay) {
+            endOfStream = true; // we've read everything there is
+        }
+        if (endOfStream) {
+            return false;
+        }
+
+        // Request more data immediately
+        output.write(getBodyMessageArray);
+
+        boolean moreData = receive();
+        if( !moreData ) {
+            endOfStream = true;
+        }
+        return moreData;
+    }
+
+
+    /**
+     * Read an AJP message.
+     *
+     * @return true if the message has been read, false if the short read
+     *         didn't return anything
+     * @throws IOException any other failure, including incomplete reads
+     */
+    protected boolean readMessage(AjpMessage message)
+        throws IOException {
+
+        byte[] buf = message.getBuffer();
+        int headerLength = message.getHeaderLength();
+
+        read(buf, 0, headerLength);
+
+        int messageLength = message.processHeader();
+        if (messageLength < 0) {
+            // Invalid AJP header signature
+            // TODO: Throw some exception and close the connection to frontend.
+            return false;
+        }
+        else if (messageLength == 0) {
+            // Zero length message.
+            return true;
+        }
+        else {
+            read(buf, headerLength, messageLength);
+            return true;
+        }
+    }
+
+
+    /**
+     * Callback to write data from the buffer.
+     */
+    @Override
+    protected void flush(boolean explicit) throws IOException {
+        if (explicit && !finished) {
+            // Send the flush message
+            output.write(flushMessageArray);
+        }
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer implements OutputBuffer {
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res)
+            throws IOException {
+
+            if (!response.isCommitted()) {
+                // Validate and write response headers
+                try {
+                    prepareResponse();
+                } catch (IOException e) {
+                    // Set error flag
+                    error = true;
+                }
+            }
+
+            int len = chunk.getLength();
+            // 4 - hardcoded, byte[] marshaling overhead
+            // Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
+            int chunkSize = Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE;
+            int off = 0;
+            while (len > 0) {
+                int thisTime = len;
+                if (thisTime > chunkSize) {
+                    thisTime = chunkSize;
+                }
+                len -= thisTime;
+                responseHeaderMessage.reset();
+                responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+                responseHeaderMessage.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
+                responseHeaderMessage.end();
+                output.write(responseHeaderMessage.getBuffer(), 0, responseHeaderMessage.getLen());
+
+                off += thisTime;
+            }
+
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpProtocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpProtocol.java
new file mode 100644
index 0000000..26644e7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/AjpProtocol.java
@@ -0,0 +1,273 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.ajp;
+
+import java.net.Socket;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.ObjectName;
+
+import org.apache.coyote.RequestGroupInfo;
+import org.apache.coyote.RequestInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class AjpProtocol extends AbstractAjpProtocol {
+    
+    
+    private static final Log log = LogFactory.getLog(AjpProtocol.class);
+
+    @Override
+    protected Log getLog() { return log; }
+
+
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    public AjpProtocol() {
+        endpoint = new JIoEndpoint();
+        cHandler = new AjpConnectionHandler(this);
+        ((JIoEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+    }
+
+    
+    // ----------------------------------------------------- Instance Variables
+
+    
+    /**
+     * Connection handler for AJP.
+     */
+    private AjpConnectionHandler cHandler;
+
+
+    // ----------------------------------------------------- JMX related methods
+
+    @Override
+    protected String getNamePrefix() {
+        return ("ajp-bio");
+    }
+
+
+    // --------------------------------------  AjpConnectionHandler Inner Class
+
+
+    protected static class AjpConnectionHandler implements Handler {
+
+        protected AjpProtocol proto;
+        protected AtomicLong registerCount = new AtomicLong(0);
+        protected RequestGroupInfo global = new RequestGroupInfo();
+
+        protected ConcurrentHashMap<SocketWrapper<Socket>, AjpProcessor> connections =
+            new ConcurrentHashMap<SocketWrapper<Socket>, AjpProcessor>();
+
+        protected ConcurrentLinkedQueue<AjpProcessor> recycledProcessors = 
+            new ConcurrentLinkedQueue<AjpProcessor>() {
+            private static final long serialVersionUID = 1L;
+            protected AtomicInteger size = new AtomicInteger(0);
+            @Override
+            public boolean offer(AjpProcessor processor) {
+                boolean offer = (proto.processorCache == -1) ? true : (size.get() < proto.processorCache);
+                //avoid over growing our cache or add after we have stopped
+                boolean result = false;
+                if ( offer ) {
+                    result = super.offer(processor);
+                    if ( result ) {
+                        size.incrementAndGet();
+                    }
+                }
+                if (!result) unregister(processor);
+                return result;
+            }
+            
+            @Override
+            public AjpProcessor poll() {
+                AjpProcessor result = super.poll();
+                if ( result != null ) {
+                    size.decrementAndGet();
+                }
+                return result;
+            }
+            
+            @Override
+            public void clear() {
+                AjpProcessor next = poll();
+                while ( next != null ) {
+                    unregister(next);
+                    next = poll();
+                }
+                super.clear();
+                size.set(0);
+            }
+        };
+
+        public AjpConnectionHandler(AjpProtocol proto) {
+            this.proto = proto;
+        }
+        
+        @Override
+        public Object getGlobal() {
+            return global;
+        }
+
+        @Override
+        public SSLImplementation getSslImplementation() {
+            // AJP does not support SSL
+            return null;
+        }
+
+        @Override
+        public void recycle() {
+            recycledProcessors.clear();
+        }
+        
+        @Override
+        public SocketState process(SocketWrapper<Socket> socket) {
+            return process(socket,SocketStatus.OPEN);
+        }
+
+        @Override
+        public SocketState process(SocketWrapper<Socket> socket, SocketStatus status) {
+            AjpProcessor processor = connections.remove(socket);
+            try {
+                if (processor == null) {
+                    processor = recycledProcessors.poll();
+                }
+                if (processor == null) {
+                    processor = createProcessor();
+                }
+
+                SocketState state = socket.isAsync()?processor.asyncDispatch(status):processor.process(socket);
+                if (state == SocketState.LONG) {
+                    connections.put(socket, processor);
+                    socket.setAsync(true);
+                    // longPoll may change socket state (e.g. to trigger a
+                    // complete or dispatch)
+                    return processor.asyncPostProcess();
+                } else {
+                    socket.setAsync(false);
+                    processor.recycle();
+                    recycledProcessors.offer(processor);
+                }
+                return state;
+            } catch(java.net.SocketException e) {
+                // SocketExceptions are normal
+                log.debug(sm.getString(
+                        "ajpprotocol.proto.socketexception.debug"), e);
+            } catch (java.io.IOException e) {
+                // IOExceptions are normal
+                log.debug(sm.getString(
+                        "ajpprotocol.proto.ioexception.debug"), e);
+            }
+            // Future developers: if you discover any other
+            // rare-but-nonfatal exceptions, catch them here, and log as
+            // above.
+            catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                // any other exception or error is odd. Here we log it
+                // with "ERROR" level, so it will show up even on
+                // less-than-verbose logs.
+                log.error(sm.getString("ajpprotocol.proto.error"), e);
+            }
+            processor.recycle();
+            recycledProcessors.offer(processor);
+            return SocketState.CLOSED;
+        }
+
+        protected AjpProcessor createProcessor() {
+            AjpProcessor processor = new AjpProcessor(proto.packetSize, (JIoEndpoint)proto.endpoint);
+            processor.setAdapter(proto.adapter);
+            processor.setTomcatAuthentication(proto.tomcatAuthentication);
+            processor.setRequiredSecret(proto.requiredSecret);
+            processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
+            processor.setClientCertProvider(proto.getClientCertProvider());
+            register(processor);
+            return processor;
+        }
+        
+        protected void register(AjpProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        long count = registerCount.incrementAndGet();
+                        RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(global);
+                        ObjectName rpName = new ObjectName
+                            (proto.getDomain() + ":type=RequestProcessor,worker="
+                                + proto.getName() + ",name=AjpRequest" + count);
+                        if (log.isDebugEnabled()) {
+                            log.debug("Register " + rpName);
+                        }
+                        Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                        rp.setRpName(rpName);
+                    } catch (Exception e) {
+                        log.warn("Error registering request");
+                    }
+                }
+            }
+        }
+
+        protected void unregister(AjpProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(null);
+                        ObjectName rpName = rp.getRpName();
+                        if (log.isDebugEnabled()) {
+                            log.debug("Unregister " + rpName);
+                        }
+                        Registry.getRegistry(null, null).unregisterComponent(rpName);
+                        rp.setRpName(null);
+                    } catch (Exception e) {
+                        log.warn("Error unregistering request", e);
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/Constants.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/Constants.java
new file mode 100644
index 0000000..49c95a7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/Constants.java
@@ -0,0 +1,422 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.ajp;
+
+import java.util.Hashtable;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+
+
+/**
+ * Constants.
+ *
+ * @author Remy Maucherat
+ */
+public final class Constants {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     * Package name.
+     */
+    public static final String Package = "org.apache.coyote.ajp";
+
+    public static final int DEFAULT_CONNECTION_LINGER = -1;
+    public static final int DEFAULT_CONNECTION_TIMEOUT = -1;
+    public static final int DEFAULT_CONNECTION_UPLOAD_TIMEOUT = 300000;
+    public static final boolean DEFAULT_TCP_NO_DELAY = true;
+    public static final boolean DEFAULT_USE_SENDFILE = false;
+
+    // Prefix codes for message types from server to container
+    public static final byte JK_AJP13_FORWARD_REQUEST   = 2;
+    public static final byte JK_AJP13_SHUTDOWN          = 7;
+    public static final byte JK_AJP13_PING_REQUEST      = 8;
+    public static final byte JK_AJP13_CPING_REQUEST     = 10;
+
+    // Prefix codes for message types from container to server
+    public static final byte JK_AJP13_SEND_BODY_CHUNK   = 3;
+    public static final byte JK_AJP13_SEND_HEADERS      = 4;
+    public static final byte JK_AJP13_END_RESPONSE      = 5;
+    public static final byte JK_AJP13_GET_BODY_CHUNK    = 6;
+    public static final byte JK_AJP13_CPONG_REPLY       = 9;
+
+    // Integer codes for common response header strings
+    public static final int SC_RESP_CONTENT_TYPE        = 0xA001;
+    public static final int SC_RESP_CONTENT_LANGUAGE    = 0xA002;
+    public static final int SC_RESP_CONTENT_LENGTH      = 0xA003;
+    public static final int SC_RESP_DATE                = 0xA004;
+    public static final int SC_RESP_LAST_MODIFIED       = 0xA005;
+    public static final int SC_RESP_LOCATION            = 0xA006;
+    public static final int SC_RESP_SET_COOKIE          = 0xA007;
+    public static final int SC_RESP_SET_COOKIE2         = 0xA008;
+    public static final int SC_RESP_SERVLET_ENGINE      = 0xA009;
+    public static final int SC_RESP_STATUS              = 0xA00A;
+    public static final int SC_RESP_WWW_AUTHENTICATE    = 0xA00B;
+    public static final int SC_RESP_AJP13_MAX           = 11;
+
+    // Integer codes for common (optional) request attribute names
+    public static final byte SC_A_CONTEXT       = 1;  // XXX Unused
+    public static final byte SC_A_SERVLET_PATH  = 2;  // XXX Unused
+    public static final byte SC_A_REMOTE_USER   = 3;
+    public static final byte SC_A_AUTH_TYPE     = 4;
+    public static final byte SC_A_QUERY_STRING  = 5;
+    public static final byte SC_A_JVM_ROUTE     = 6;
+    public static final byte SC_A_SSL_CERT      = 7;
+    public static final byte SC_A_SSL_CIPHER    = 8;
+    public static final byte SC_A_SSL_SESSION   = 9;
+    public static final byte SC_A_SSL_KEYSIZE   = 11;
+    public static final byte SC_A_SECRET        = 12;
+    public static final byte SC_A_STORED_METHOD = 13;
+
+    // Used for attributes which are not in the list above
+    public static final byte SC_A_REQ_ATTRIBUTE = 10;
+
+    /**
+     * AJP private request attributes
+     */
+    public static final String SC_A_REQ_REMOTE_PORT = "AJP_REMOTE_PORT";
+
+    // Terminates list of attributes
+    public static final byte SC_A_ARE_DONE      = (byte)0xFF;
+
+    // Ajp13 specific -  needs refactoring for the new model
+
+    /**
+     * Default maximum total byte size for a AJP packet
+     */
+    public static final int MAX_PACKET_SIZE = 8192;
+    /**
+     * Size of basic packet header
+     */
+    public static final int H_SIZE = 4;
+
+    /**
+     * Size of the header metadata
+     */
+    public static final int  READ_HEAD_LEN = 6;
+    public static final int  SEND_HEAD_LEN = 8;
+
+    /**
+     * Default maximum size of data that can be sent in one packet
+     */
+    public static final int  MAX_READ_SIZE = MAX_PACKET_SIZE - READ_HEAD_LEN;
+    public static final int  MAX_SEND_SIZE = MAX_PACKET_SIZE - SEND_HEAD_LEN;
+
+    // Translates integer codes to names of HTTP methods
+    private static final String [] methodTransArray = {
+            "OPTIONS",
+            "GET",
+            "HEAD",
+            "POST",
+            "PUT",
+            "DELETE",
+            "TRACE",
+            "PROPFIND",
+            "PROPPATCH",
+            "MKCOL",
+            "COPY",
+            "MOVE",
+            "LOCK",
+            "UNLOCK",
+            "ACL",
+            "REPORT",
+            "VERSION-CONTROL",
+            "CHECKIN",
+            "CHECKOUT",
+            "UNCHECKOUT",
+            "SEARCH",
+            "MKWORKSPACE",
+            "UPDATE",
+            "LABEL",
+            "MERGE",
+            "BASELINE-CONTROL",
+            "MKACTIVITY"
+    };
+    
+    /**
+     * Converts an AJP coded HTTP method to the method name.
+     * @param code the coded value
+     * @return the string value of the method
+     */
+    public static final String getMethodForCode(final int code) {
+        return methodTransArray[code];
+    }
+
+    public static final int SC_M_JK_STORED = (byte) 0xFF;
+
+    // id's for common request headers
+    public static final int SC_REQ_ACCEPT          = 1;
+    public static final int SC_REQ_ACCEPT_CHARSET  = 2;
+    public static final int SC_REQ_ACCEPT_ENCODING = 3;
+    public static final int SC_REQ_ACCEPT_LANGUAGE = 4;
+    public static final int SC_REQ_AUTHORIZATION   = 5;
+    public static final int SC_REQ_CONNECTION      = 6;
+    public static final int SC_REQ_CONTENT_TYPE    = 7;
+    public static final int SC_REQ_CONTENT_LENGTH  = 8;
+    public static final int SC_REQ_COOKIE          = 9;
+    public static final int SC_REQ_COOKIE2         = 10;
+    public static final int SC_REQ_HOST            = 11;
+    public static final int SC_REQ_PRAGMA          = 12;
+    public static final int SC_REQ_REFERER         = 13;
+    public static final int SC_REQ_USER_AGENT      = 14;
+    // AJP14 new header
+    public static final byte SC_A_SSL_KEY_SIZE  = 11; // XXX ???
+
+    // Translates integer codes to request header names
+    private static final String [] headerTransArray = {
+            "accept",
+            "accept-charset",
+            "accept-encoding",
+            "accept-language",
+            "authorization",
+            "connection",
+            "content-type",
+            "content-length",
+            "cookie",
+            "cookie2",
+            "host",
+            "pragma",
+            "referer",
+            "user-agent"
+    };
+
+    /**
+     * Converts an AJP coded HTTP request header to the header name.
+     * @param code the coded value
+     * @return the string value of the header name
+     */
+    public static final String getHeaderForCode(final int code) {
+        return headerTransArray[code];
+    }
+
+    // Translates integer codes to response header names
+    private static final String [] responseTransArray = {
+            "Content-Type",
+            "Content-Language",
+            "Content-Length",
+            "Date",
+            "Last-Modified",
+            "Location",
+            "Set-Cookie",
+            "Set-Cookie2",
+            "Servlet-Engine",
+            "Status",
+            "WWW-Authenticate"
+    };
+    
+    /**
+     * Converts an AJP coded response header name to the HTTP response header name.
+     * @param code the coded value
+     * @return the string value of the header
+     */
+    public static final String getResponseHeaderForCode(final int code) {
+        return responseTransArray[code];
+    }
+
+    private static final Hashtable<String,Integer>  responseTransHash =
+        new Hashtable<String,Integer>(20);
+
+    static {
+        try {
+            int i;
+            for (i = 0; i < SC_RESP_AJP13_MAX; i++) {
+                responseTransHash.put(getResponseHeaderForCode(i),
+                        Integer.valueOf(0xA001 + i));
+            }
+        }
+        catch (Exception e) {
+            // Do nothing
+        }
+    }    
+
+    public static final int getResponseAjpIndex(String header)
+    {
+        Integer i = responseTransHash.get(header);
+        if (i == null)
+            return 0;
+        else
+            return i.intValue();
+    }
+
+    
+    /**
+     * CRLF.
+     */
+    public static final String CRLF = "\r\n";
+
+
+    /**
+     * Server string.
+     */
+    public static final byte[] SERVER_BYTES =
+        ByteChunk.convertToBytes("Server: Apache-Coyote/1.1" + CRLF);
+
+
+    /**
+     * CR.
+     */
+    public static final byte CR = (byte) '\r';
+
+
+    /**
+     * LF.
+     */
+    public static final byte LF = (byte) '\n';
+
+
+    /**
+     * SP.
+     */
+    public static final byte SP = (byte) ' ';
+
+
+    /**
+     * HT.
+     */
+    public static final byte HT = (byte) '\t';
+
+
+    /**
+     * COLON.
+     */
+    public static final byte COLON = (byte) ':';
+
+
+    /**
+     * 'A'.
+     */
+    public static final byte A = (byte) 'A';
+
+
+    /**
+     * 'a'.
+     */
+    public static final byte a = (byte) 'a';
+
+
+    /**
+     * 'Z'.
+     */
+    public static final byte Z = (byte) 'Z';
+
+
+    /**
+     * '?'.
+     */
+    public static final byte QUESTION = (byte) '?';
+
+
+    /**
+     * Lower case offset.
+     */
+    public static final byte LC_OFFSET = A - a;
+
+
+    /**
+     * Default HTTP header buffer size.
+     */
+    public static final int DEFAULT_HTTP_HEADER_BUFFER_SIZE = 48 * 1024;
+
+
+    /* Various constant "strings" */
+    public static final byte[] CRLF_BYTES = ByteChunk.convertToBytes(CRLF);
+    public static final byte[] COLON_BYTES = ByteChunk.convertToBytes(": ");
+    public static final String CONNECTION = "Connection";
+    public static final String CLOSE = "close";
+    public static final byte[] CLOSE_BYTES =
+        ByteChunk.convertToBytes(CLOSE);
+    public static final String KEEPALIVE = "keep-alive";
+    public static final byte[] KEEPALIVE_BYTES =
+        ByteChunk.convertToBytes(KEEPALIVE);
+    public static final String CHUNKED = "chunked";
+    public static final byte[] ACK_BYTES =
+        ByteChunk.convertToBytes("HTTP/1.1 100 Continue" + CRLF + CRLF);
+    public static final String TRANSFERENCODING = "Transfer-Encoding";
+    public static final byte[] _200_BYTES =
+        ByteChunk.convertToBytes("200");
+    public static final byte[] _400_BYTES =
+        ByteChunk.convertToBytes("400");
+    public static final byte[] _404_BYTES =
+        ByteChunk.convertToBytes("404");
+
+
+    /**
+     * Identity filters (input and output).
+     */
+    public static final int IDENTITY_FILTER = 0;
+
+
+    /**
+     * Chunked filters (input and output).
+     */
+    public static final int CHUNKED_FILTER = 1;
+
+
+    /**
+     * Void filters (input and output).
+     */
+    public static final int VOID_FILTER = 2;
+
+
+    /**
+     * GZIP filter (output).
+     */
+    public static final int GZIP_FILTER = 3;
+
+
+    /**
+     * Buffered filter (input)
+     */
+    public static final int BUFFERED_FILTER = 3;
+
+
+    /**
+     * HTTP/1.0.
+     */
+    public static final String HTTP_10 = "HTTP/1.0";
+
+
+    /**
+     * HTTP/1.1.
+     */
+    public static final String HTTP_11 = "HTTP/1.1";
+    public static final byte[] HTTP_11_BYTES =
+        ByteChunk.convertToBytes(HTTP_11);
+
+
+    /**
+     * GET.
+     */
+    public static final String GET = "GET";
+
+
+    /**
+     * HEAD.
+     */
+    public static final String HEAD = "HEAD";
+
+
+    /**
+     * POST.
+     */
+    public static final String POST = "POST";
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/LocalStrings.properties
new file mode 100644
index 0000000..ce6763f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/LocalStrings.properties
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# $Id: LocalStrings.properties,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+
+# language 
+
+# package org.apache.coyote.ajp
+
+#
+# AjpAprProtocol
+#
+
+ajpprotocol.endpoint.starterror=Error starting endpoint
+ajpprotocol.init=Initializing Coyote AJP/1.3 on {0}
+ajpprotocol.proto.error=Error reading request, ignored
+ajpprotocol.start=Starting Coyote AJP/1.3 on {0}
+ajpprotocol.failedread=Socket read failed
+ajpprotocol.failedwrite=Socket write failed
+ajpprotocol.request.register=Error registering request processor in JMX
+
+ajpprocessor.failedflush=Failed to flush AJP message
+ajpprocessor.failedsend=Failed to send AJP message
+ajpprocessor.header.error=Header message parsing failed
+ajpprocessor.request.prepare=Error preparing request
+ajpprocessor.request.process=Error processing request
+ajpprocessor.certs.fail=Certificate conversion failed
+ajpprocessor.socket.info=Exception getting socket information
+
+ajpmessage.null=Cannot append null value
+ajpmessage.overflow=Overflow error for buffer adding {0} bytes at position {1}
+ajpmessage.read=Requested {0} bytes exceeds message available data
+ajpmessage.invalid=Invalid message received with signature {0}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/LocalStrings_es.properties
new file mode 100644
index 0000000..a32037a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ajp/LocalStrings_es.properties
@@ -0,0 +1,30 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+ajpprotocol.endpoint.starterror = Error arrancando punto final
+ajpprotocol.proto.error = Error leyendo requerimiento, ignorado
+ajpprotocol.start = Arrancando Coyote AJP/1.3 en {0}
+ajpprotocol.failedread = Fallo en lectura de Conector
+ajpprotocol.failedwrite = Fallo en escritura de Conector
+ajpprotocol.request.register = Error registrando procesador de requerimiento en JMX
+ajpprocessor.header.error = Fallo en an\u00E1lisis de mensaje de cabecera
+ajpprocessor.request.prepare = Error preparando requerimiento
+ajpprocessor.request.process = Error procesando requerimiento
+ajpprocessor.certs.fail = Fallo en conversi\u00F3n de Certificado
+ajpprocessor.socket.info = Excepci\u00F3n obteniendo informaci\u00F3n de conector
+ajpmessage.null = No puedo a\u00F1adir valor nulo
+ajpmessage.overflow = Error de desbordamiento en b\u00FAfer al a\u00F1adir {0} bytes en posici\u00F3n {1}
+ajpmessage.read = Los {0} bytes requeridos exceden los datos disponibles de mensaje
+ajpmessage.invalid = Mensaje inv\u00E1lido recibido con firma {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java
new file mode 100644
index 0000000..2f79756
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java
@@ -0,0 +1,121 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http11;
+
+import org.apache.tomcat.util.net.SSLImplementation;
+
+public abstract class AbstractHttp11JsseProtocol
+        extends AbstractHttp11Protocol {
+
+    protected SSLImplementation sslImplementation = null;
+
+    public String getAlgorithm() { return endpoint.getAlgorithm();}
+    public void setAlgorithm(String s ) { endpoint.setAlgorithm(s);}
+    
+    public String getClientAuth() { return endpoint.getClientAuth();}
+    public void setClientAuth(String s ) { endpoint.setClientAuth(s);}
+
+    public String getKeystoreFile() { return endpoint.getKeystoreFile();}
+    public void setKeystoreFile(String s ) { endpoint.setKeystoreFile(s);}
+
+    public String getKeystorePass() { return endpoint.getKeystorePass();}
+    public void setKeystorePass(String s ) { endpoint.setKeystorePass(s);}
+    
+    public String getKeystoreType() { return endpoint.getKeystoreType();}
+    public void setKeystoreType(String s ) { endpoint.setKeystoreType(s);}
+
+    public String getKeystoreProvider() {
+        return endpoint.getKeystoreProvider();
+    }
+    public void setKeystoreProvider(String s ) {
+        endpoint.setKeystoreProvider(s);
+    }
+
+    public String getSslProtocol() { return endpoint.getSslProtocol();}
+    public void setSslProtocol(String s) { endpoint.setSslProtocol(s);}
+    
+    public String getCiphers() { return endpoint.getCiphers();}
+    public void setCiphers(String s) { endpoint.setCiphers(s);}
+
+    public String getKeyAlias() { return endpoint.getKeyAlias();}
+    public void setKeyAlias(String s ) { endpoint.setKeyAlias(s);}
+
+    public String getKeyPass() { return endpoint.getKeyPass();}
+    public void setKeyPass(String s ) { endpoint.setKeyPass(s);}
+    
+    public void setTruststoreFile(String f){ endpoint.setTruststoreFile(f);}
+    public String getTruststoreFile(){ return endpoint.getTruststoreFile();}
+
+    public void setTruststorePass(String p){ endpoint.setTruststorePass(p);}
+    public String getTruststorePass(){return endpoint.getTruststorePass();}
+
+    public void setTruststoreType(String t){ endpoint.setTruststoreType(t);}
+    public String getTruststoreType(){ return endpoint.getTruststoreType();}
+
+    public void setTruststoreProvider(String t){
+        endpoint.setTruststoreProvider(t);
+    }
+    public String getTruststoreProvider(){
+        return endpoint.getTruststoreProvider();
+    }
+
+    public void setTruststoreAlgorithm(String a){
+        endpoint.setTruststoreAlgorithm(a);
+    }
+    public String getTruststoreAlgorithm(){
+        return endpoint.getTruststoreAlgorithm();
+    }
+    
+    public void setTrustMaxCertLength(String s){
+        endpoint.setTrustMaxCertLength(s);
+    }
+    public String getTrustMaxCertLength(){
+        return endpoint.getTrustMaxCertLength();
+    }
+    
+    public void setCrlFile(String s){endpoint.setCrlFile(s);}
+    public String getCrlFile(){ return endpoint.getCrlFile();}
+    
+    public void setSessionCacheSize(String s){endpoint.setSessionCacheSize(s);}
+    public String getSessionCacheSize(){ return endpoint.getSessionCacheSize();}
+
+    public void setSessionTimeout(String s){endpoint.setSessionTimeout(s);}
+    public String getSessionTimeout(){ return endpoint.getSessionTimeout();}
+    
+    public void setAllowUnsafeLegacyRenegotiation(String s) {
+        endpoint.setAllowUnsafeLegacyRenegotiation(s);
+    }
+    public String getAllowUnsafeLegacyRenegotiation() {
+        return endpoint.getAllowUnsafeLegacyRenegotiation();
+    }
+
+    private String sslImplemenationName = null;
+    public String getSslImplemenationName() { return sslImplemenationName; }
+    public void setSslImplemenationName(String s) {
+        this.sslImplemenationName = s;
+    }
+
+    // ------------------------------------------------------- Lifecycle methods
+
+    @Override
+    public void init() throws Exception {
+        // SSL implementation needs to be in place before end point is
+        // initialized
+        sslImplementation = SSLImplementation.getInstance(sslImplemenationName);
+        super.init();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11Processor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11Processor.java
new file mode 100644
index 0000000..b212b51
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11Processor.java
@@ -0,0 +1,1020 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.ActionHook;
+import org.apache.coyote.Adapter;
+import org.apache.coyote.AsyncContextCallback;
+import org.apache.coyote.AsyncStateMachine;
+import org.apache.coyote.Processor;
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.filters.BufferedInputFilter;
+import org.apache.coyote.http11.filters.ChunkedInputFilter;
+import org.apache.coyote.http11.filters.ChunkedOutputFilter;
+import org.apache.coyote.http11.filters.GzipOutputFilter;
+import org.apache.coyote.http11.filters.IdentityInputFilter;
+import org.apache.coyote.http11.filters.IdentityOutputFilter;
+import org.apache.coyote.http11.filters.SavedRequestInputFilter;
+import org.apache.coyote.http11.filters.VoidInputFilter;
+import org.apache.coyote.http11.filters.VoidOutputFilter;
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.Ascii;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class AbstractHttp11Processor implements ActionHook, Processor {
+
+    protected abstract Log getLog();
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /*
+     * Tracks how many internal filters are in the filter library so they
+     * are skipped when looking for pluggable filters. 
+     */
+    private int pluggableFilterIndex = Integer.MAX_VALUE;
+    
+    /**
+     * Associated adapter.
+     */
+    protected Adapter adapter = null;
+
+
+    /**
+     * Request object.
+     */
+    protected Request request = null;
+
+
+    /**
+     * Response object.
+     */
+    protected Response response = null;
+
+
+    /**
+     * Error flag.
+     */
+    protected boolean error = false;
+
+
+    /**
+     * Keep-alive.
+     */
+    protected boolean keepAlive = true;
+
+
+    /**
+     * HTTP/1.1 flag.
+     */
+    protected boolean http11 = true;
+
+
+    /**
+     * HTTP/0.9 flag.
+     */
+    protected boolean http09 = false;
+
+
+    /**
+     * Content delimiter for the request (if false, the connection will
+     * be closed at the end of the request).
+     */
+    protected boolean contentDelimitation = true;
+
+
+    /**
+     * Is there an expectation ?
+     */
+    protected boolean expectation = false;
+
+
+    /**
+     * Regular expression that defines the restricted user agents.
+     */
+    protected Pattern restrictedUserAgents = null;
+
+
+    /**
+     * Maximum number of Keep-Alive requests to honor.
+     */
+    protected int maxKeepAliveRequests = -1;
+
+    /**
+     * The number of seconds Tomcat will wait for a subsequent request
+     * before closing the connection.
+     */
+    protected int keepAliveTimeout = -1;
+
+    /**
+     * Remote Address associated with the current connection.
+     */
+    protected String remoteAddr = null;
+
+
+    /**
+     * Remote Host associated with the current connection.
+     */
+    protected String remoteHost = null;
+
+
+    /**
+     * Local Host associated with the current connection.
+     */
+    protected String localName = null;
+
+
+    /**
+     * Local port to which the socket is connected
+     */
+    protected int localPort = -1;
+
+
+    /**
+     * Remote port to which the socket is connected
+     */
+    protected int remotePort = -1;
+
+
+    /**
+     * The local Host address.
+     */
+    protected String localAddr = null;
+
+
+    /**
+     * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server.
+     */
+    protected int connectionUploadTimeout = 300000;
+
+
+    /**
+     * Flag to disable setting a different time-out on uploads.
+     */
+    protected boolean disableUploadTimeout = false;
+
+
+    /**
+     * Allowed compression level.
+     */
+    protected int compressionLevel = 0;
+
+
+    /**
+     * Minimum content size to make compression.
+     */
+    protected int compressionMinSize = 2048;
+
+
+    /**
+     * Socket buffering.
+     */
+    protected int socketBuffer = -1;
+
+
+    /**
+     * Max saved post size.
+     */
+    protected int maxSavePostSize = 4 * 1024;
+
+
+    /**
+     * Regular expression that defines the user agents to not use gzip with
+     */
+    protected Pattern noCompressionUserAgents = null;
+
+    /**
+     * List of MIMES which could be gzipped
+     */
+    protected String[] compressableMimeTypes =
+    { "text/html", "text/xml", "text/plain" };
+
+
+    /**
+     * Host name (used to avoid useless B2C conversion on the host name).
+     */
+    protected char[] hostNameC = new char[0];
+
+
+    /**
+     * Allow a customized the server header for the tin-foil hat folks.
+     */
+    protected String server = null;
+
+    
+    /**
+     * Track changes in state for async requests.
+     */
+    protected AsyncStateMachine asyncStateMachine = new AsyncStateMachine(this);
+
+
+    /**
+     * Set compression level.
+     */
+    public void setCompression(String compression) {
+        if (compression.equals("on")) {
+            this.compressionLevel = 1;
+        } else if (compression.equals("force")) {
+            this.compressionLevel = 2;
+        } else if (compression.equals("off")) {
+            this.compressionLevel = 0;
+        } else {
+            try {
+                // Try to parse compression as an int, which would give the
+                // minimum compression size
+                compressionMinSize = Integer.parseInt(compression);
+                this.compressionLevel = 1;
+            } catch (Exception e) {
+                this.compressionLevel = 0;
+            }
+        }
+    }
+
+    /**
+     * Set Minimum size to trigger compression.
+     */
+    public void setCompressionMinSize(int compressionMinSize) {
+        this.compressionMinSize = compressionMinSize;
+    }
+
+
+    /**
+     * Set no compression user agent pattern. Regular expression as supported
+     * by {@link Pattern}.
+     *
+     * ie: "gorilla|desesplorer|tigrus"
+     */
+    public void setNoCompressionUserAgents(String noCompressionUserAgents) {
+        if (noCompressionUserAgents == null || noCompressionUserAgents.length() == 0) {
+            this.noCompressionUserAgents = null;
+        } else {
+            this.noCompressionUserAgents =
+                Pattern.compile(noCompressionUserAgents);
+        }
+    }
+
+    /**
+     * Add a mime-type which will be compressible
+     * The mime-type String will be exactly matched
+     * in the response mime-type header .
+     *
+     * @param mimeType mime-type string
+     */
+    public void addCompressableMimeType(String mimeType) {
+        compressableMimeTypes =
+            addStringArray(compressableMimeTypes, mimeType);
+    }
+
+
+    /**
+     * Set compressible mime-type list (this method is best when used with
+     * a large number of connectors, where it would be better to have all of
+     * them referenced a single array).
+     */
+    public void setCompressableMimeTypes(String[] compressableMimeTypes) {
+        this.compressableMimeTypes = compressableMimeTypes;
+    }
+
+
+    /**
+     * Set compressable mime-type list
+     * List contains users agents separated by ',' :
+     *
+     * ie: "text/html,text/xml,text/plain"
+     */
+    public void setCompressableMimeTypes(String compressableMimeTypes) {
+        if (compressableMimeTypes != null) {
+            this.compressableMimeTypes = null;
+            StringTokenizer st = new StringTokenizer(compressableMimeTypes, ",");
+
+            while (st.hasMoreTokens()) {
+                addCompressableMimeType(st.nextToken().trim());
+            }
+        }
+    }
+
+
+    /**
+     * Return compression level.
+     */
+    public String getCompression() {
+        switch (compressionLevel) {
+        case 0:
+            return "off";
+        case 1:
+            return "on";
+        case 2:
+            return "force";
+        }
+        return "off";
+    }
+
+
+    /**
+     * General use method
+     *
+     * @param sArray the StringArray
+     * @param value string
+     */
+    private String[] addStringArray(String sArray[], String value) {
+        String[] result = null;
+        if (sArray == null) {
+            result = new String[1];
+            result[0] = value;
+        }
+        else {
+            result = new String[sArray.length + 1];
+            for (int i = 0; i < sArray.length; i++)
+                result[i] = sArray[i];
+            result[sArray.length] = value;
+        }
+        return result;
+    }
+
+
+    /**
+     * Checks if any entry in the string array starts with the specified value
+     *
+     * @param sArray the StringArray
+     * @param value string
+     */
+    private boolean startsWithStringArray(String sArray[], String value) {
+        if (value == null)
+           return false;
+        for (int i = 0; i < sArray.length; i++) {
+            if (value.startsWith(sArray[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Set restricted user agent list (which will downgrade the connector
+     * to HTTP/1.0 mode). Regular expression as supported by {@link Pattern}.
+     *
+     * ie: "gorilla|desesplorer|tigrus"
+     */
+    public void setRestrictedUserAgents(String restrictedUserAgents) {
+        if (restrictedUserAgents == null ||
+                restrictedUserAgents.length() == 0) {
+            this.restrictedUserAgents = null;
+        } else {
+            this.restrictedUserAgents = Pattern.compile(restrictedUserAgents);
+        }
+    }
+
+
+    /**
+     * Set the maximum number of Keep-Alive requests to honor.
+     * This is to safeguard from DoS attacks.  Setting to a negative
+     * value disables the check.
+     */
+    public void setMaxKeepAliveRequests(int mkar) {
+        maxKeepAliveRequests = mkar;
+    }
+
+
+    /**
+     * Return the number of Keep-Alive requests that we will honor.
+     */
+    public int getMaxKeepAliveRequests() {
+        return maxKeepAliveRequests;
+    }
+
+    /**
+     * Set the Keep-Alive timeout.
+     */
+    public void setKeepAliveTimeout(int timeout) {
+        keepAliveTimeout = timeout;
+    }
+
+
+    /**
+     * Return the number Keep-Alive timeout.
+     */
+    public int getKeepAliveTimeout() {
+        return keepAliveTimeout;
+    }
+
+
+    /**
+     * Set the maximum size of a POST which will be buffered in SSL mode.
+     */
+    public void setMaxSavePostSize(int msps) {
+        maxSavePostSize = msps;
+    }
+
+
+    /**
+     * Return the maximum size of a POST which will be buffered in SSL mode.
+     */
+    public int getMaxSavePostSize() {
+        return maxSavePostSize;
+    }
+
+
+    /**
+     * Set the flag to control upload time-outs.
+     */
+    public void setDisableUploadTimeout(boolean isDisabled) {
+        disableUploadTimeout = isDisabled;
+    }
+
+    /**
+     * Get the flag that controls upload time-outs.
+     */
+    public boolean getDisableUploadTimeout() {
+        return disableUploadTimeout;
+    }
+
+    /**
+     * Set the socket buffer flag.
+     */
+    public void setSocketBuffer(int socketBuffer) {
+        this.socketBuffer = socketBuffer;
+    }
+
+    /**
+     * Get the socket buffer flag.
+     */
+    public int getSocketBuffer() {
+        return socketBuffer;
+    }
+
+    /**
+     * Set the upload timeout.
+     */
+    public void setConnectionUploadTimeout(int timeout) {
+        connectionUploadTimeout = timeout ;
+    }
+
+    /**
+     * Get the upload timeout.
+     */
+    public int getConnectionUploadTimeout() {
+        return connectionUploadTimeout;
+    }
+
+
+    /**
+     * Set the server header name.
+     */
+    public void setServer( String server ) {
+        if (server==null || server.equals("")) {
+            this.server = null;
+        } else {
+            this.server = server;
+        }
+    }
+
+    /**
+     * Get the server header name.
+     */
+    public String getServer() {
+        return server;
+    }
+
+
+    /** Get the request associated with this processor.
+     *
+     * @return The request
+     */
+    public Request getRequest() {
+        return request;
+    }
+
+
+    /**
+     * Set the associated adapter.
+     *
+     * @param adapter the new adapter
+     */
+    public void setAdapter(Adapter adapter) {
+        this.adapter = adapter;
+    }
+
+
+    /**
+     * Get the associated adapter.
+     *
+     * @return the associated adapter
+     */
+    public Adapter getAdapter() {
+        return adapter;
+    }
+
+
+    /**
+     * Check if the resource could be compressed, if the client supports it.
+     */
+    private boolean isCompressable() {
+
+        // Check if content is not already gzipped
+        MessageBytes contentEncodingMB =
+            response.getMimeHeaders().getValue("Content-Encoding");
+
+        if ((contentEncodingMB != null)
+            && (contentEncodingMB.indexOf("gzip") != -1))
+            return false;
+
+        // If force mode, always compress (test purposes only)
+        if (compressionLevel == 2)
+           return true;
+
+        // Check if sufficient length to trigger the compression
+        long contentLength = response.getContentLengthLong();
+        if ((contentLength == -1)
+            || (contentLength > compressionMinSize)) {
+            // Check for compatible MIME-TYPE
+            if (compressableMimeTypes != null) {
+                return (startsWithStringArray(compressableMimeTypes,
+                                              response.getContentType()));
+            }
+        }
+
+        return false;
+    }
+
+    
+    /**
+     * Check if compression should be used for this resource. Already checked
+     * that the resource could be compressed if the client supports it.
+     */
+    private boolean useCompression() {
+
+        // Check if browser support gzip encoding
+        MessageBytes acceptEncodingMB =
+            request.getMimeHeaders().getValue("accept-encoding");
+
+        if ((acceptEncodingMB == null)
+            || (acceptEncodingMB.indexOf("gzip") == -1))
+            return false;
+
+        // If force mode, always compress (test purposes only)
+        if (compressionLevel == 2)
+           return true;
+
+        // Check for incompatible Browser
+        if (noCompressionUserAgents != null) {
+            MessageBytes userAgentValueMB =
+                request.getMimeHeaders().getValue("user-agent");
+            if(userAgentValueMB != null) {
+                String userAgentValue = userAgentValueMB.toString();
+
+                if (noCompressionUserAgents != null &&
+                        noCompressionUserAgents.matcher(userAgentValue).matches()) {
+                        return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    
+    /**
+     * Specialized utility method: find a sequence of lower case bytes inside
+     * a ByteChunk.
+     */
+    protected int findBytes(ByteChunk bc, byte[] b) {
+
+        byte first = b[0];
+        byte[] buff = bc.getBuffer();
+        int start = bc.getStart();
+        int end = bc.getEnd();
+
+        // Look for first char
+        int srcEnd = b.length;
+
+        for (int i = start; i <= (end - srcEnd); i++) {
+            if (Ascii.toLower(buff[i]) != first) continue;
+            // found first char, now look for a match
+            int myPos = i+1;
+            for (int srcPos = 1; srcPos < srcEnd;) {
+                if (Ascii.toLower(buff[myPos++]) != b[srcPos++])
+                    break;
+                if (srcPos == srcEnd) return i - start; // found it
+            }
+        }
+        return -1;
+    }
+
+    
+    /**
+     * Determine if we must drop the connection because of the HTTP status
+     * code.  Use the same list of codes as Apache/httpd.
+     */
+    protected boolean statusDropsConnection(int status) {
+        return status == 400 /* SC_BAD_REQUEST */ ||
+               status == 408 /* SC_REQUEST_TIMEOUT */ ||
+               status == 411 /* SC_LENGTH_REQUIRED */ ||
+               status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||
+               status == 414 /* SC_REQUEST_URI_TOO_LONG */ ||
+               status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||
+               status == 503 /* SC_SERVICE_UNAVAILABLE */ ||
+               status == 501 /* SC_NOT_IMPLEMENTED */;
+    }
+
+
+    /**
+     * Exposes input buffer to super class to allow better code re-use.
+     * @return  The input buffer used by the processor. 
+     */
+    protected abstract AbstractInputBuffer getInputBuffer();
+
+    
+    /**
+     * Exposes output buffer to super class to allow better code re-use.
+     * @return  The output buffer used by the processor. 
+     */
+    protected abstract AbstractOutputBuffer getOutputBuffer();
+
+    
+    /**
+     * Initialize standard input and output filters.
+     */
+    protected void initializeFilters(int maxTrailerSize) {
+        // Create and add the identity filters.
+        getInputBuffer().addFilter(new IdentityInputFilter());
+        getOutputBuffer().addFilter(new IdentityOutputFilter());
+
+        // Create and add the chunked filters.
+        getInputBuffer().addFilter(new ChunkedInputFilter(maxTrailerSize));
+        getOutputBuffer().addFilter(new ChunkedOutputFilter());
+
+        // Create and add the void filters.
+        getInputBuffer().addFilter(new VoidInputFilter());
+        getOutputBuffer().addFilter(new VoidOutputFilter());
+
+        // Create and add buffered input filter
+        getInputBuffer().addFilter(new BufferedInputFilter());
+
+        // Create and add the chunked filters.
+        //getInputBuffer().addFilter(new GzipInputFilter());
+        getOutputBuffer().addFilter(new GzipOutputFilter());
+        
+        pluggableFilterIndex = getInputBuffer().getFilters().length;
+    }
+
+    
+    /**
+     * Add an input filter to the current request.
+     *
+     * @return false if the encoding was not found (which would mean it is
+     * unsupported)
+     */
+    protected boolean addInputFilter(InputFilter[] inputFilters,
+                                     String encodingName) {
+        if (encodingName.equals("identity")) {
+            // Skip
+        } else if (encodingName.equals("chunked")) {
+            getInputBuffer().addActiveFilter
+                (inputFilters[Constants.CHUNKED_FILTER]);
+            contentDelimitation = true;
+        } else {
+            for (int i = pluggableFilterIndex; i < inputFilters.length; i++) {
+                if (inputFilters[i].getEncodingName()
+                    .toString().equals(encodingName)) {
+                    getInputBuffer().addActiveFilter(inputFilters[i]);
+                    return true;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @Override
+    public final void action(ActionCode actionCode, Object param) {
+        
+        if (actionCode == ActionCode.COMMIT) {
+            // Commit current response
+
+            if (response.isCommitted())
+                return;
+
+            // Validate and write response headers
+            try {
+                prepareResponse();
+                getOutputBuffer().commit();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+            }
+        } else if (actionCode == ActionCode.ACK) {
+            // Acknowledge request
+            // Send a 100 status back if it makes sense (response not committed
+            // yet, and client specified an expectation for 100-continue)
+
+            if ((response.isCommitted()) || !expectation)
+                return;
+
+            getInputBuffer().setSwallowInput(true);
+            try {
+                getOutputBuffer().sendAck();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+            }
+        } else if (actionCode == ActionCode.CLIENT_FLUSH) {
+
+            try {
+                getOutputBuffer().flush();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+                response.setErrorException(e);
+            }
+
+        } else if (actionCode == ActionCode.DISABLE_SWALLOW_INPUT) {
+            // Do not swallow request input but
+            // make sure we are closing the connection
+            error = true;
+            getInputBuffer().setSwallowInput(false);
+
+        } else if (actionCode == ActionCode.RESET) {
+            // Reset response
+            // Note: This must be called before the response is committed
+
+            getOutputBuffer().reset();
+
+        } else if (actionCode == ActionCode.CUSTOM) {
+            // Do nothing
+            // TODO Remove this action
+
+        } else if (actionCode == ActionCode.REQ_SET_BODY_REPLAY) {
+            ByteChunk body = (ByteChunk) param;
+            
+            InputFilter savedBody = new SavedRequestInputFilter(body);
+            savedBody.setRequest(request);
+
+            AbstractInputBuffer internalBuffer = (AbstractInputBuffer)
+                request.getInputBuffer();
+            internalBuffer.addActiveFilter(savedBody);
+        } else if (actionCode == ActionCode.ASYNC_START) {
+            asyncStateMachine.asyncStart((AsyncContextCallback) param);
+        } else if (actionCode == ActionCode.ASYNC_DISPATCHED) {
+            asyncStateMachine.asyncDispatched();
+        } else if (actionCode == ActionCode.ASYNC_TIMEOUT) {
+            AtomicBoolean result = (AtomicBoolean) param;
+            result.set(asyncStateMachine.asyncTimeout());
+        } else if (actionCode == ActionCode.ASYNC_RUN) {
+            asyncStateMachine.asyncRun((Runnable) param);
+        } else if (actionCode == ActionCode.ASYNC_ERROR) {
+            asyncStateMachine.asyncError();
+        } else if (actionCode == ActionCode.ASYNC_IS_STARTED) {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncStarted());
+        } else if (actionCode == ActionCode.ASYNC_IS_DISPATCHING) {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncDispatching());
+        } else if (actionCode == ActionCode.ASYNC_IS_ASYNC) {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsync());
+        } else if (actionCode == ActionCode.ASYNC_IS_TIMINGOUT) {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
+        } else {
+            actionInternal(actionCode, param);
+        }
+    }
+    
+    abstract void actionInternal(ActionCode actionCode, Object param);
+    
+
+    /**
+     * When committing the response, we have to validate the set of headers, as
+     * well as setup the response filters.
+     */
+    private void prepareResponse() {
+
+        boolean entityBody = true;
+        contentDelimitation = false;
+
+        OutputFilter[] outputFilters = getOutputBuffer().getFilters();
+
+        if (http09 == true) {
+            // HTTP/0.9
+            getOutputBuffer().addActiveFilter
+                (outputFilters[Constants.IDENTITY_FILTER]);
+            return;
+        }
+
+        int statusCode = response.getStatus();
+        if ((statusCode == 204) || (statusCode == 205)
+            || (statusCode == 304)) {
+            // No entity body
+            getOutputBuffer().addActiveFilter
+                (outputFilters[Constants.VOID_FILTER]);
+            entityBody = false;
+            contentDelimitation = true;
+        }
+
+        MessageBytes methodMB = request.method();
+        if (methodMB.equals("HEAD")) {
+            // No entity body
+            getOutputBuffer().addActiveFilter
+                (outputFilters[Constants.VOID_FILTER]);
+            contentDelimitation = true;
+        }
+
+        // Sendfile support
+        boolean sendingWithSendfile = false;
+        if (getEndpoint().getUseSendfile()) {
+            sendingWithSendfile = prepareSendfile(outputFilters);
+        }
+        
+        // Check for compression
+        boolean isCompressable = false;
+        boolean useCompression = false;
+        if (entityBody && (compressionLevel > 0) && !sendingWithSendfile) {
+            isCompressable = isCompressable();
+            if (isCompressable) {
+                useCompression = useCompression();
+            }
+            // Change content-length to -1 to force chunking
+            if (useCompression) {
+                response.setContentLength(-1);
+            }
+        }
+
+        MimeHeaders headers = response.getMimeHeaders();
+        if (!entityBody) {
+            response.setContentLength(-1);
+        } else {
+            String contentType = response.getContentType();
+            if (contentType != null) {
+                headers.setValue("Content-Type").setString(contentType);
+            }
+            String contentLanguage = response.getContentLanguage();
+            if (contentLanguage != null) {
+                headers.setValue("Content-Language")
+                    .setString(contentLanguage);
+            }
+        }
+
+        long contentLength = response.getContentLengthLong();
+        if (contentLength != -1) {
+            headers.setValue("Content-Length").setLong(contentLength);
+            getOutputBuffer().addActiveFilter
+                (outputFilters[Constants.IDENTITY_FILTER]);
+            contentDelimitation = true;
+        } else {
+            if (entityBody && http11) {
+                getOutputBuffer().addActiveFilter
+                    (outputFilters[Constants.CHUNKED_FILTER]);
+                contentDelimitation = true;
+                headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
+            } else {
+                getOutputBuffer().addActiveFilter
+                    (outputFilters[Constants.IDENTITY_FILTER]);
+            }
+        }
+
+        if (useCompression) {
+            getOutputBuffer().addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
+            headers.setValue("Content-Encoding").setString("gzip");
+        }
+        // If it might be compressed, set the Vary header
+        if (isCompressable) {
+            // Make Proxies happy via Vary (from mod_deflate)
+            MessageBytes vary = headers.getValue("Vary");
+            if (vary == null) {
+                // Add a new Vary header
+                headers.setValue("Vary").setString("Accept-Encoding");
+            } else if (vary.equals("*")) {
+                // No action required
+            } else {
+                // Merge into current header
+                headers.setValue("Vary").setString(
+                        vary.getString() + ",Accept-Encoding");
+            }
+        }
+
+        // Add date header
+        headers.setValue("Date").setString(FastHttpDateFormat.getCurrentDate());
+
+        // FIXME: Add transfer encoding header
+
+        if ((entityBody) && (!contentDelimitation)) {
+            // Mark as close the connection after the request, and add the
+            // connection: close header
+            keepAlive = false;
+        }
+
+        // If we know that the request is bad this early, add the
+        // Connection: close header.
+        keepAlive = keepAlive && !statusDropsConnection(statusCode);
+        if (!keepAlive) {
+            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
+        } else if (!http11 && !error) {
+            headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);
+        }
+
+        // Build the response header
+        getOutputBuffer().sendStatus();
+
+        // Add server header
+        if (server != null) {
+            // Always overrides anything the app might set
+            headers.setValue("Server").setString(server);
+        } else if (headers.getValue("Server") == null) {
+            // If app didn't set the header, use the default
+            getOutputBuffer().write(Constants.SERVER_BYTES);
+        }
+
+        int size = headers.size();
+        for (int i = 0; i < size; i++) {
+            getOutputBuffer().sendHeader(headers.getName(i), headers.getValue(i));
+        }
+        getOutputBuffer().endHeaders();
+
+    }
+
+    abstract AbstractEndpoint getEndpoint();
+    abstract boolean prepareSendfile(OutputFilter[] outputFilters);
+    
+    public void endRequest() {
+
+        // Finish the handling of the request
+        try {
+            getInputBuffer().endRequest();
+        } catch (IOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            getLog().error(sm.getString("http11processor.request.finish"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+        try {
+            getOutputBuffer().endRequest();
+        } catch (IOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            getLog().error(sm.getString("http11processor.response.finish"), t);
+            error = true;
+        }
+
+    }
+    
+    public final void recycle() {
+        getInputBuffer().recycle();
+        getOutputBuffer().recycle();
+        asyncStateMachine.recycle();
+        recycleInternal();
+    }
+    
+    protected abstract void recycleInternal();
+
+    @Override
+    public abstract Executor getExecutor();
+    
+    protected boolean isAsync() {
+        return asyncStateMachine.isAsync();
+    }
+    
+    protected SocketState asyncPostProcess() {
+        return asyncStateMachine.asyncPostProcess();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11Protocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11Protocol.java
new file mode 100644
index 0000000..cf62f0c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -0,0 +1,179 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http11;
+
+import org.apache.coyote.AbstractProtocolHandler;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class AbstractHttp11Protocol extends AbstractProtocolHandler {
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ------------------------------------------------ HTTP specific properties
+    // ------------------------------------------ managed in the ProtocolHandler
+
+    private int socketBuffer = 9000;
+    public int getSocketBuffer() { return socketBuffer; }
+    public void setSocketBuffer(int socketBuffer) {
+        this.socketBuffer = socketBuffer;
+    }
+
+    
+    /**
+     * Maximum size of the post which will be saved when processing certain
+     * requests, such as a POST.
+     */
+    private int maxSavePostSize = 4 * 1024;
+    public int getMaxSavePostSize() { return maxSavePostSize; }
+    public void setMaxSavePostSize(int valueI) { maxSavePostSize = valueI; }
+    
+
+    /**
+     * Maximum size of the HTTP message header.
+     */
+    private int maxHttpHeaderSize = 8 * 1024;
+    public int getMaxHttpHeaderSize() { return maxHttpHeaderSize; }
+    public void setMaxHttpHeaderSize(int valueI) { maxHttpHeaderSize = valueI; }
+
+    
+    /**
+     * Specifies a different (usually  longer) connection timeout during data
+     * upload. 
+     */
+    private int connectionUploadTimeout = 300000;
+    public int getConnectionUploadTimeout() { return connectionUploadTimeout; }
+    public void setConnectionUploadTimeout(int i) {
+        connectionUploadTimeout = i;
+    }
+
+
+    /**
+     * If true, the connectionUploadTimeout will be ignored and the regular
+     * socket timeout will be used for the full duration of the connection.
+     */
+    private boolean disableUploadTimeout = true;
+    public boolean getDisableUploadTimeout() { return disableUploadTimeout; }
+    public void setDisableUploadTimeout(boolean isDisabled) {
+        disableUploadTimeout = isDisabled;
+    }
+
+
+    /**
+     * Integrated compression support.
+     */
+    private String compression = "off";
+    public String getCompression() { return compression; }
+    public void setCompression(String valueS) { compression = valueS; }
+
+
+    private String noCompressionUserAgents = null;
+    public String getNoCompressionUserAgents() {
+        return noCompressionUserAgents;
+    }
+    public void setNoCompressionUserAgents(String valueS) {
+        noCompressionUserAgents = valueS;
+    }
+
+
+    private String compressableMimeTypes = "text/html,text/xml,text/plain";
+    public String getCompressableMimeType() { return compressableMimeTypes; }
+    public void setCompressableMimeType(String valueS) {
+        compressableMimeTypes = valueS;
+    }
+    public String getCompressableMimeTypes() {
+        return getCompressableMimeType();
+    }
+    public void setCompressableMimeTypes(String valueS) {
+        setCompressableMimeType(valueS);
+    }
+
+
+    private int compressionMinSize = 2048;
+    public int getCompressionMinSize() { return compressionMinSize; }
+    public void setCompressionMinSize(int valueI) {
+        compressionMinSize = valueI;
+    }
+
+
+    /**
+     * Regular expression that defines the User agents which should be
+     * restricted to HTTP/1.0 support.
+     */
+    private String restrictedUserAgents = null;
+    public String getRestrictedUserAgents() { return restrictedUserAgents; }
+    public void setRestrictedUserAgents(String valueS) {
+        restrictedUserAgents = valueS;
+    }
+
+
+    /**
+     * Server header.
+     */
+    private String server;
+    public String getServer() { return server; }
+    public void setServer( String server ) {
+        this.server = server;
+    }
+
+
+    /**
+     * Maximum size of trailing headers in bytes
+     */
+    private int maxTrailerSize = 8192;
+    public int getMaxTrailerSize() { return maxTrailerSize; }
+    public void setMaxTrailerSize(int maxTrailerSize) {
+        this.maxTrailerSize = maxTrailerSize;
+    }
+
+
+    /**
+     * This field indicates if the protocol is treated as if it is secure. This
+     * normally means https is being used but can be used to fake https e.g
+     * behind a reverse proxy.
+     */
+    private boolean secure;
+    public boolean getSecure() { return secure; }
+    public void setSecure(boolean b) { 
+        secure = b;         
+    }
+    
+
+    // ------------------------------------------------ HTTP specific properties
+    // ------------------------------------------ passed through to the EndPoint
+    
+    public boolean isSSLEnabled() { return endpoint.isSSLEnabled();}
+    public void setSSLEnabled(boolean SSLEnabled) {
+        endpoint.setSSLEnabled(SSLEnabled);
+    }    
+
+
+    /**
+     * Maximum number of requests which can be performed over a keepalive 
+     * connection. The default is the same as for Apache HTTP Server.
+     */
+    public int getMaxKeepAliveRequests() { 
+        return endpoint.getMaxKeepAliveRequests();
+    }
+    public void setMaxKeepAliveRequests(int mkar) {
+        endpoint.setMaxKeepAliveRequests(mkar);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractInputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractInputBuffer.java
new file mode 100644
index 0000000..aab5a4d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractInputBuffer.java
@@ -0,0 +1,313 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class AbstractInputBuffer implements InputBuffer{
+
+    public abstract boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException;
+    
+    public abstract boolean parseHeaders() throws IOException;
+    
+    protected abstract boolean fill(boolean block) throws IOException; 
+
+    // -------------------------------------------------------------- Constants
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated Coyote request.
+     */
+    protected Request request;
+
+
+    /**
+     * Headers of the associated request.
+     */
+    protected MimeHeaders headers;
+
+
+    /**
+     * State.
+     */
+    protected boolean parsingHeader;
+
+
+    /**
+     * Swallow input ? (in the case of an expectation)
+     */
+    protected boolean swallowInput;
+
+
+    /**
+     * Pointer to the current read buffer.
+     */
+    protected byte[] buf;
+
+
+    /**
+     * Last valid byte.
+     */
+    protected int lastValid;
+
+
+    /**
+     * Position in the buffer.
+     */
+    protected int pos;
+
+
+    /**
+     * Pos of the end of the header in the buffer, which is also the
+     * start of the body.
+     */
+    protected int end;
+
+
+    /**
+     * Underlying input stream.
+     */
+    protected InputStream inputStream;
+
+
+    /**
+     * Underlying input buffer.
+     */
+    protected InputBuffer inputStreamInputBuffer;
+
+
+    /**
+     * Filter library.
+     * Note: Filter[0] is always the "chunked" filter.
+     */
+    protected InputFilter[] filterLibrary;
+
+
+    /**
+     * Active filters (in order).
+     */
+    protected InputFilter[] activeFilters;
+
+
+    /**
+     * Index of the last active filter.
+     */
+    protected int lastActiveFilter;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the underlying socket input stream.
+     */
+    public void setInputStream(InputStream inputStream) {
+
+        // FIXME: Check for null ?
+
+        this.inputStream = inputStream;
+
+    }
+
+
+    /**
+     * Get the underlying socket input stream.
+     */
+    public InputStream getInputStream() {
+
+        return inputStream;
+
+    }
+
+
+    /**
+     * Add an input filter to the filter library.
+     */
+    public void addFilter(InputFilter filter) {
+
+        // FIXME: Check for null ?
+
+        InputFilter[] newFilterLibrary = 
+            new InputFilter[filterLibrary.length + 1];
+        for (int i = 0; i < filterLibrary.length; i++) {
+            newFilterLibrary[i] = filterLibrary[i];
+        }
+        newFilterLibrary[filterLibrary.length] = filter;
+        filterLibrary = newFilterLibrary;
+
+        activeFilters = new InputFilter[filterLibrary.length];
+
+    }
+
+
+    /**
+     * Get filters.
+     */
+    public InputFilter[] getFilters() {
+
+        return filterLibrary;
+
+    }
+
+
+    /**
+     * Add an input filter to the filter library.
+     */
+    public void addActiveFilter(InputFilter filter) {
+
+        if (lastActiveFilter == -1) {
+            filter.setBuffer(inputStreamInputBuffer);
+        } else {
+            for (int i = 0; i <= lastActiveFilter; i++) {
+                if (activeFilters[i] == filter)
+                    return;
+            }
+            filter.setBuffer(activeFilters[lastActiveFilter]);
+        }
+
+        activeFilters[++lastActiveFilter] = filter;
+
+        filter.setRequest(request);
+
+    }
+
+
+    /**
+     * Set the swallow input flag.
+     */
+    public void setSwallowInput(boolean swallowInput) {
+        this.swallowInput = swallowInput;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the input buffer. This should be called when closing the 
+     * connection.
+     */
+    public void recycle() {
+
+        // Recycle Request object
+        request.recycle();
+
+        inputStream = null;
+        lastValid = 0;
+        pos = 0;
+        lastActiveFilter = -1;
+        parsingHeader = true;
+        swallowInput = true;
+
+    }
+
+
+    /**
+     * End processing of current HTTP request.
+     * Note: All bytes of the current request should have been already 
+     * consumed. This method only resets all the pointers so that we are ready
+     * to parse the next HTTP request.
+     */
+    public void nextRequest() {
+
+        // Recycle Request object
+        request.recycle();
+
+        // Copy leftover bytes to the beginning of the buffer
+        if (lastValid - pos > 0) {
+            int npos = 0;
+            int opos = pos;
+            while (lastValid - opos > opos - npos) {
+                System.arraycopy(buf, opos, buf, npos, opos - npos);
+                npos += pos;
+                opos += pos;
+            }
+            System.arraycopy(buf, opos, buf, npos, lastValid - opos);
+        }
+
+        // Recycle filters
+        for (int i = 0; i <= lastActiveFilter; i++) {
+            activeFilters[i].recycle();
+        }
+
+        // Reset pointers
+        lastValid = lastValid - pos;
+        pos = 0;
+        lastActiveFilter = -1;
+        parsingHeader = true;
+        swallowInput = true;
+
+    }
+
+
+    /**
+     * End request (consumes leftover bytes).
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    public void endRequest()
+        throws IOException {
+
+        if (swallowInput && (lastActiveFilter != -1)) {
+            int extraBytes = (int) activeFilters[lastActiveFilter].end();
+            pos = pos - extraBytes;
+        }
+
+    }
+    
+    // ---------------------------------------------------- InputBuffer Methods
+
+
+    /**
+     * Read some bytes.
+     */
+    @Override
+    public int doRead(ByteChunk chunk, Request req) 
+        throws IOException {
+
+        if (lastActiveFilter == -1)
+            return inputStreamInputBuffer.doRead(chunk, req);
+        else
+            return activeFilters[lastActiveFilter].doRead(chunk,req);
+
+    }
+
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractOutputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractOutputBuffer.java
new file mode 100644
index 0000000..b4a4345
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/AbstractOutputBuffer.java
@@ -0,0 +1,542 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.filters.GzipOutputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.HttpMessages;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class AbstractOutputBuffer implements OutputBuffer{
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated Coyote response.
+     */
+    protected Response response;
+
+
+    /**
+     * Committed flag.
+     */
+    protected boolean committed;
+
+
+    /**
+     * Finished flag.
+     */
+    protected boolean finished;
+
+
+    /**
+     * The buffer used for header composition.
+     */
+    protected byte[] buf;
+
+
+    /**
+     * Position in the buffer.
+     */
+    protected int pos;
+
+
+    /**
+     * Filter library.
+     * Note: Filter[0] is always the "chunked" filter.
+     */
+    protected OutputFilter[] filterLibrary;
+
+
+    /**
+     * Active filter (which is actually the top of the pipeline).
+     */
+    protected OutputFilter[] activeFilters;
+
+
+    /**
+     * Index of the last active filter.
+     */
+    protected int lastActiveFilter;
+
+    /**
+     * Underlying output buffer.
+     */
+    protected OutputBuffer outputStreamOutputBuffer;
+
+    /**
+     * Bytes written to client for the current request
+     */
+    protected long byteCount = 0;
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+    
+    /**
+     * Logger.
+     */
+    private static final org.apache.juli.logging.Log log
+        = org.apache.juli.logging.LogFactory.getLog(AbstractOutputBuffer.class);
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Add an output filter to the filter library.
+     */
+    public void addFilter(OutputFilter filter) {
+
+        OutputFilter[] newFilterLibrary = 
+            new OutputFilter[filterLibrary.length + 1];
+        for (int i = 0; i < filterLibrary.length; i++) {
+            newFilterLibrary[i] = filterLibrary[i];
+        }
+        newFilterLibrary[filterLibrary.length] = filter;
+        filterLibrary = newFilterLibrary;
+
+        activeFilters = new OutputFilter[filterLibrary.length];
+
+    }
+
+
+    /**
+     * Get filters.
+     */
+    public OutputFilter[] getFilters() {
+
+        return filterLibrary;
+
+    }
+
+
+    /**
+     * Add an output filter to the filter library.
+     */
+    public void addActiveFilter(OutputFilter filter) {
+
+        if (lastActiveFilter == -1) {
+            filter.setBuffer(outputStreamOutputBuffer);
+        } else {
+            for (int i = 0; i <= lastActiveFilter; i++) {
+                if (activeFilters[i] == filter)
+                    return;
+            }
+            filter.setBuffer(activeFilters[lastActiveFilter]);
+        }
+
+        activeFilters[++lastActiveFilter] = filter;
+
+        filter.setResponse(response);
+
+    }
+
+
+ // --------------------------------------------------- OutputBuffer Methods
+
+
+    /**
+     * Write the contents of a byte chunk.
+     * 
+     * @param chunk byte chunk
+     * @return number of bytes written
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    public int doWrite(ByteChunk chunk, Response res) 
+        throws IOException {
+
+        if (!committed) {
+
+            // Send the connector a request for commit. The connector should
+            // then validate the headers, send them (using sendHeaders) and 
+            // set the filters accordingly.
+            response.action(ActionCode.COMMIT, null);
+
+        }
+
+        if (lastActiveFilter == -1)
+            return outputStreamOutputBuffer.doWrite(chunk, res);
+        else
+            return activeFilters[lastActiveFilter].doWrite(chunk, res);
+
+    }
+
+
+    @Override
+    public long getBytesWritten() {
+        if (lastActiveFilter == -1) {
+            return outputStreamOutputBuffer.getBytesWritten();
+        } else {
+            return activeFilters[lastActiveFilter].getBytesWritten();
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Flush the response.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    public void flush()
+        throws IOException {
+
+        if (!committed) {
+
+            // Send the connector a request for commit. The connector should
+            // then validate the headers, send them (using sendHeader) and 
+            // set the filters accordingly.
+            response.action(ActionCode.COMMIT, null);
+
+        }
+        
+        // go through the filters and if there is gzip filter
+        // invoke it to flush
+        for (int i = 0; i <= lastActiveFilter; i++) {
+            if (activeFilters[i] instanceof GzipOutputFilter) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Flushing the gzip filter at position " + i +
+                            " of the filter chain...");
+                }
+                ((GzipOutputFilter) activeFilters[i]).flush();
+                break;
+            }
+        }
+    }
+    
+    /**
+     * Reset current response.
+     * 
+     * @throws IllegalStateException if the response has already been committed
+     */
+    public void reset() {
+
+        if (committed)
+            throw new IllegalStateException(/*FIXME:Put an error message*/);
+
+        // Recycle Request object
+        response.recycle();
+
+    }
+    
+    /**
+     * Recycle the output buffer. This should be called when closing the 
+     * connection.
+     */
+    public void recycle() {
+        // Recycle filters
+        for (int i = 0; i <= lastActiveFilter; i++) {
+            activeFilters[i].recycle();
+        }
+        // Recycle Request object
+        response.recycle();
+        pos = 0;
+        lastActiveFilter = -1;
+        committed = false;
+        finished = false;
+
+    }
+    
+    /**
+     * End processing of current HTTP request.
+     * Note: All bytes of the current request should have been already 
+     * consumed. This method only resets all the pointers so that we are ready
+     * to parse the next HTTP request.
+     */
+    public void nextRequest() {
+
+        // Recycle Request object
+        response.recycle();
+        // Recycle filters
+        for (int i = 0; i <= lastActiveFilter; i++) {
+            activeFilters[i].recycle();
+        }
+
+        // Reset pointers
+        pos = 0;
+        lastActiveFilter = -1;
+        committed = false;
+        finished = false;
+        byteCount = 0;
+    }
+    
+    /**
+     * End request.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    public void endRequest()
+        throws IOException {
+
+        if (!committed) {
+
+            // Send the connector a request for commit. The connector should
+            // then validate the headers, send them (using sendHeader) and 
+            // set the filters accordingly.
+            response.action(ActionCode.COMMIT, null);
+
+        }
+
+        if (finished)
+            return;
+
+        if (lastActiveFilter != -1)
+            activeFilters[lastActiveFilter].end();
+        finished = true;
+    }
+    
+    public abstract void sendAck() throws IOException;
+    
+    protected abstract void commit() throws IOException;
+
+
+    /**
+     * Send the response status line.
+     */
+    public void sendStatus() {
+
+        // Write protocol name
+        write(Constants.HTTP_11_BYTES);
+        buf[pos++] = Constants.SP;
+
+        // Write status code
+        int status = response.getStatus();
+        switch (status) {
+        case 200:
+            write(Constants._200_BYTES);
+            break;
+        case 400:
+            write(Constants._400_BYTES);
+            break;
+        case 404:
+            write(Constants._404_BYTES);
+            break;
+        default:
+            write(status);
+        }
+
+        buf[pos++] = Constants.SP;
+
+        // Write message
+        String message = null;
+        if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
+                HttpMessages.isSafeInHttpHeader(response.getMessage())) {
+            message = response.getMessage();
+        }
+        if (message == null) {
+            write(HttpMessages.getMessage(status)); 
+        } else {
+            write(message);
+        }
+
+        // End the response status line
+        if (org.apache.coyote.Constants.IS_SECURITY_ENABLED){
+           AccessController.doPrivileged(
+                new PrivilegedAction<Void>(){
+                    @Override
+                    public Void run(){
+                        buf[pos++] = Constants.CR;
+                        buf[pos++] = Constants.LF;
+                        return null;
+                    }
+                }
+           );
+        } else {
+            buf[pos++] = Constants.CR;
+            buf[pos++] = Constants.LF;
+        }
+
+    } 
+
+
+    /**
+     * Send a header.
+     * 
+     * @param name Header name
+     * @param value Header value
+     */
+    public void sendHeader(MessageBytes name, MessageBytes value) {
+
+        write(name);
+        buf[pos++] = Constants.COLON;
+        buf[pos++] = Constants.SP;
+        write(value);
+        buf[pos++] = Constants.CR;
+        buf[pos++] = Constants.LF;
+
+    }
+
+
+    /**
+     * End the header block.
+     */
+    public void endHeaders() {
+
+        buf[pos++] = Constants.CR;
+        buf[pos++] = Constants.LF;
+
+    }
+    
+    
+    /**
+     * This method will write the contents of the specified message bytes 
+     * buffer to the output stream, without filtering. This method is meant to
+     * be used to write the response header.
+     * 
+     * @param mb data to be written
+     */
+    protected void write(MessageBytes mb) {
+
+        if (mb.getType() == MessageBytes.T_BYTES) {
+            ByteChunk bc = mb.getByteChunk();
+            write(bc);
+        } else if (mb.getType() == MessageBytes.T_CHARS) {
+            CharChunk cc = mb.getCharChunk();
+            write(cc);
+        } else {
+            write(mb.toString());
+        }
+
+    }
+
+
+    /**
+     * This method will write the contents of the specified message bytes 
+     * buffer to the output stream, without filtering. This method is meant to
+     * be used to write the response header.
+     * 
+     * @param bc data to be written
+     */
+    protected void write(ByteChunk bc) {
+
+        // Writing the byte chunk to the output buffer
+        int length = bc.getLength();
+        System.arraycopy(bc.getBytes(), bc.getStart(), buf, pos, length);
+        pos = pos + length;
+
+    }
+
+
+    /**
+     * This method will write the contents of the specified char 
+     * buffer to the output stream, without filtering. This method is meant to
+     * be used to write the response header.
+     * 
+     * @param cc data to be written
+     */
+    protected void write(CharChunk cc) {
+
+        int start = cc.getStart();
+        int end = cc.getEnd();
+        char[] cbuf = cc.getBuffer();
+        for (int i = start; i < end; i++) {
+            char c = cbuf[i];
+            // Note:  This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework.  It must suffice until servlet output
+            // streams properly encode their output.
+            if ((c <= 31) && (c != 9)) {
+                c = ' ';
+            } else if (c == 127) {
+                c = ' ';
+            }
+            buf[pos++] = (byte) c;
+        }
+
+    }
+
+
+    /**
+     * This method will write the contents of the specified byte 
+     * buffer to the output stream, without filtering. This method is meant to
+     * be used to write the response header.
+     * 
+     * @param b data to be written
+     */
+    public void write(byte[] b) {
+
+        // Writing the byte chunk to the output buffer
+        System.arraycopy(b, 0, buf, pos, b.length);
+        pos = pos + b.length;
+
+    }
+
+
+    /**
+     * This method will write the contents of the specified String to the 
+     * output stream, without filtering. This method is meant to be used to 
+     * write the response header.
+     * 
+     * @param s data to be written
+     */
+    protected void write(String s) {
+
+        if (s == null)
+            return;
+
+        // From the Tomcat 3.3 HTTP/1.0 connector
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt (i);
+            // Note:  This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework.  It must suffice until servlet output
+            // streams properly encode their output.
+            if ((c <= 31) && (c != 9)) {
+                c = ' ';
+            } else if (c == 127) {
+                c = ' ';
+            }
+            buf[pos++] = (byte) c;
+        }
+
+    }
+
+
+    /**
+     * This method will print the specified integer to the output stream, 
+     * without filtering. This method is meant to be used to write the 
+     * response header.
+     * 
+     * @param i data to be written
+     */
+    protected void write(int i) {
+
+        write(String.valueOf(i));
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Constants.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Constants.java
new file mode 100644
index 0000000..20edc93
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Constants.java
@@ -0,0 +1,209 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+
+
+/**
+ * Constants.
+ *
+ * @author Remy Maucherat
+ */
+public final class Constants {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     * Package name.
+     */
+    public static final String Package = "org.apache.coyote.http11";
+
+    public static final int DEFAULT_CONNECTION_LINGER = -1;
+    public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
+    public static final boolean DEFAULT_TCP_NO_DELAY = true;
+    
+    
+    /**
+     * CRLF.
+     */
+    public static final String CRLF = "\r\n";
+
+    
+    /**
+     * Server string.
+     */
+    public static final byte[] SERVER_BYTES = 
+        ByteChunk.convertToBytes("Server: Apache-Coyote/1.1" + CRLF);
+
+    
+    /**
+     * CR.
+     */
+    public static final byte CR = (byte) '\r';
+
+
+    /**
+     * LF.
+     */
+    public static final byte LF = (byte) '\n';
+
+
+    /**
+     * SP.
+     */
+    public static final byte SP = (byte) ' ';
+
+
+    /**
+     * HT.
+     */
+    public static final byte HT = (byte) '\t';
+
+
+    /**
+     * COLON.
+     */
+    public static final byte COLON = (byte) ':';
+    
+    /**
+     * SEMI_COLON.
+     */
+    public static final byte SEMI_COLON = (byte) ';';
+
+
+
+    /**
+     * 'A'.
+     */
+    public static final byte A = (byte) 'A';
+
+
+    /**
+     * 'a'.
+     */
+    public static final byte a = (byte) 'a';
+
+
+    /**
+     * 'Z'.
+     */
+    public static final byte Z = (byte) 'Z';
+
+
+    /**
+     * '?'.
+     */
+    public static final byte QUESTION = (byte) '?';
+
+
+    /**
+     * Lower case offset.
+     */
+    public static final byte LC_OFFSET = A - a;
+
+
+    /* Various constant "strings" */
+    public static final String CONNECTION = "Connection";
+    public static final String CLOSE = "close";
+    public static final byte[] CLOSE_BYTES = 
+        ByteChunk.convertToBytes(CLOSE);
+    public static final String KEEPALIVE = "keep-alive";
+    public static final byte[] KEEPALIVE_BYTES = 
+        ByteChunk.convertToBytes(KEEPALIVE);
+    public static final String CHUNKED = "chunked";
+    public static final byte[] ACK_BYTES = 
+        ByteChunk.convertToBytes("HTTP/1.1 100 Continue" + CRLF + CRLF);
+    public static final String TRANSFERENCODING = "Transfer-Encoding";
+    public static final byte[] _200_BYTES = 
+        ByteChunk.convertToBytes("200");
+    public static final byte[] _400_BYTES = 
+        ByteChunk.convertToBytes("400");
+    public static final byte[] _404_BYTES = 
+        ByteChunk.convertToBytes("404");
+    
+
+    /**
+     * Identity filters (input and output).
+     */
+    public static final int IDENTITY_FILTER = 0;
+
+
+    /**
+     * Chunked filters (input and output).
+     */
+    public static final int CHUNKED_FILTER = 1;
+
+
+    /**
+     * Void filters (input and output).
+     */
+    public static final int VOID_FILTER = 2;
+
+
+    /**
+     * GZIP filter (output).
+     */
+    public static final int GZIP_FILTER = 3;
+
+
+    /**
+     * Buffered filter (input)
+     */
+    public static final int BUFFERED_FILTER = 3;
+
+
+    /**
+     * HTTP/1.0.
+     */
+    public static final String HTTP_10 = "HTTP/1.0";
+
+
+    /**
+     * HTTP/1.1.
+     */
+    public static final String HTTP_11 = "HTTP/1.1";
+    public static final byte[] HTTP_11_BYTES = 
+        ByteChunk.convertToBytes(HTTP_11);
+
+
+    /**
+     * GET.
+     */
+    public static final String GET = "GET";
+
+
+    /**
+     * HEAD.
+     */
+    public static final String HEAD = "HEAD";
+
+
+    /**
+     * POST.
+     */
+    public static final String POST = "POST";
+
+    /**
+     * Has security been turned on?
+     */
+    public static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11AprProcessor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11AprProcessor.java
new file mode 100644
index 0000000..98d15d0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11AprProcessor.java
@@ -0,0 +1,942 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Request;
+import org.apache.coyote.RequestInfo;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.filters.BufferedInputFilter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Address;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLSocket;
+import org.apache.tomcat.jni.Sockaddr;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Processes HTTP requests.
+ *
+ * @author Remy Maucherat
+ */
+public class Http11AprProcessor extends AbstractHttp11Processor {
+
+
+    private static final Log log = LogFactory.getLog(Http11AprProcessor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public Http11AprProcessor(int headerBufferSize, AprEndpoint endpoint,
+            int maxTrailerSize) {
+
+        this.endpoint = endpoint;
+        
+        request = new Request();
+        inputBuffer = new InternalAprInputBuffer(request, headerBufferSize);
+        request.setInputBuffer(inputBuffer);
+
+        response = new Response();
+        response.setHook(this);
+        outputBuffer = new InternalAprOutputBuffer(response, headerBufferSize);
+        response.setOutputBuffer(outputBuffer);
+        request.setResponse(response);
+        
+        ssl = endpoint.isSSLEnabled();
+
+        initializeFilters(maxTrailerSize);
+
+        // Cause loading of HexUtils
+        HexUtils.load();
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Input.
+     */
+    protected InternalAprInputBuffer inputBuffer = null;
+
+
+    /**
+     * Output.
+     */
+    protected InternalAprOutputBuffer outputBuffer = null;
+
+
+    /**
+     * Sendfile data.
+     */
+    protected AprEndpoint.SendfileData sendfileData = null;
+
+
+    /**
+     * Comet used.
+     */
+    protected boolean comet = false;
+
+
+    /**
+     * SSL enabled ?
+     */
+    protected boolean ssl = false;
+    
+
+    /**
+     * Socket associated with the current connection.
+     */
+    protected SocketWrapper<Long> socket = null;
+
+
+    /**
+     * Associated endpoint.
+     */
+    protected AprEndpoint endpoint;
+    @Override
+    protected AbstractEndpoint getEndpoint() {
+        return endpoint;
+    }
+
+
+    /**
+     * When client certificate information is presented in a form other than
+     * instances of {@link java.security.cert.X509Certificate} it needs to be
+     * converted before it can be used and this property controls which JSSE
+     * provider is used to perform the conversion. For example it is used with
+     * the AJP connectors, the HTTP APR connector and with the
+     * {@link org.apache.catalina.valves.SSLValve}. If not specified, the
+     * default provider will be used. 
+     */
+    protected String clientCertProvider = null;
+    public String getClientCertProvider() { return clientCertProvider; }
+    public void setClientCertProvider(String s) { this.clientCertProvider = s; }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState event(SocketStatus status)
+        throws IOException {
+        
+        RequestInfo rp = request.getRequestProcessor();
+        
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            error = !adapter.event(request, response, status);
+        } catch (InterruptedIOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+        
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (error) {
+            inputBuffer.nextRequest();
+            outputBuffer.nextRequest();
+            return SocketState.CLOSED;
+        } else if (!comet) {
+            inputBuffer.nextRequest();
+            outputBuffer.nextRequest();
+            return SocketState.OPEN;
+        } else {
+            return SocketState.LONG;
+        }
+    }
+    
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState process(SocketWrapper<Long> socket)
+        throws IOException {
+        RequestInfo rp = request.getRequestProcessor();
+        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+        // Set the remote address
+        remoteAddr = null;
+        remoteHost = null;
+        localAddr = null;
+        localName = null;
+        remotePort = -1;
+        localPort = -1;
+
+        // Setting up the socket
+        this.socket = socket;
+        long socketRef = socket.getSocket().longValue();
+        inputBuffer.setSocket(socketRef);
+        outputBuffer.setSocket(socketRef);
+
+        // Error flag
+        error = false;
+        comet = false;
+        keepAlive = true;
+
+        int keepAliveLeft = maxKeepAliveRequests;
+        long soTimeout = endpoint.getSoTimeout();
+        
+        boolean keptAlive = false;
+        boolean openSocket = false;
+
+        while (!error && keepAlive && !comet && !isAsync() && !endpoint.isPaused()) {
+
+            // Parsing the request header
+            try {
+                if( !disableUploadTimeout && keptAlive && soTimeout > 0 ) {
+                    Socket.timeoutSet(socketRef, soTimeout * 1000);
+                }
+                if (!inputBuffer.parseRequestLine(keptAlive)) {
+                    // This means that no data is available right now
+                    // (long keepalive), so that the processor should be recycled
+                    // and the method should return true
+                    openSocket = true;
+                    // Add the socket to the poller
+                    endpoint.getPoller().add(socketRef);
+                    if (endpoint.isPaused()) {
+                        // 503 - Service unavailable
+                        response.setStatus(503);
+                        adapter.log(request, response, 0);
+                        error = true;
+                    } else {
+                        break;
+                    }
+                }
+                if (!endpoint.isPaused()) {
+                    request.setStartTime(System.currentTimeMillis());
+                    keptAlive = true;
+                    if (!disableUploadTimeout) {
+                        Socket.timeoutSet(socketRef,
+                                connectionUploadTimeout * 1000);
+                    }
+                    inputBuffer.parseHeaders();
+                }
+            } catch (IOException e) {
+                error = true;
+                break;
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("http11processor.header.parse"), t);
+                }
+                // 400 - Bad Request
+                response.setStatus(400);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            if (!error) {
+                // Setting up filters, and parse some request headers
+                rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+                try {
+                    prepareRequest();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("http11processor.request.prepare"), t);
+                    }
+                    // 400 - Internal Server Error
+                    response.setStatus(400);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+
+            if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)
+                keepAlive = false;
+
+            // Process the request in the adapter
+            if (!error) {
+                try {
+                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+                    adapter.service(request, response);
+                    // Handle when the response was committed before a serious
+                    // error occurred.  Throwing a ServletException should both
+                    // set the status to 500 and set the errorException.
+                    // If we fail here, then the response is likely already
+                    // committed, so we can't try and set headers.
+                    if(keepAlive && !error) { // Avoid checking twice.
+                        error = response.getErrorException() != null ||
+                                statusDropsConnection(response.getStatus());
+                    }
+                } catch (InterruptedIOException e) {
+                    error = true;
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("http11processor.request.process"), t);
+                    // 500 - Internal Server Error
+                    response.setStatus(500);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+
+            // Finish the handling of the request
+            if (!comet && !isAsync()) {
+                // If we know we are closing the connection, don't drain input.
+                // This way uploading a 100GB file doesn't tie up the thread 
+                // if the servlet has rejected it.
+                if(error)
+                    inputBuffer.setSwallowInput(false);
+                endRequest();
+            }
+
+            // If there was an error, make sure the request is counted as
+            // and error, and update the statistics counter
+            if (error) {
+                response.setStatus(500);
+            }
+            request.updateCounters();
+
+            if (!comet && !isAsync()) {
+                // Next request
+                inputBuffer.nextRequest();
+                outputBuffer.nextRequest();
+            }
+            
+            // Do sendfile as needed: add socket to sendfile and end
+            if (sendfileData != null && !error) {
+                sendfileData.socket = socketRef;
+                sendfileData.keepAlive = keepAlive;
+                if (!endpoint.getSendfile().add(sendfileData)) {
+                    openSocket = true;
+                    break;
+                }
+            }
+            
+            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (error || endpoint.isPaused()) {
+            return SocketState.CLOSED;
+        } else if (comet  || isAsync()) {
+            return SocketState.LONG;
+        } else {
+            return (openSocket) ? SocketState.OPEN : SocketState.CLOSED;
+        }
+        
+    }
+
+    /* Copied from the AjpProcessor.java */
+    public SocketState asyncDispatch(SocketWrapper<Long> socket,
+            SocketStatus status) {
+
+        // Setting up the socket
+        this.socket = socket;
+        long socketRef = socket.getSocket().longValue();
+        inputBuffer.setSocket(socketRef);
+        outputBuffer.setSocket(socketRef);
+
+        RequestInfo rp = request.getRequestProcessor();
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            error = !adapter.asyncDispatch(request, response, status);
+        } catch (InterruptedIOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (error) {
+            return SocketState.CLOSED;
+        } else if (isAsync()) {
+            return SocketState.LONG;
+        } else {
+            if (!keepAlive) {
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.OPEN;
+            }
+        }
+    }
+
+
+    @Override
+    public void recycleInternal() {
+        this.socket = null;
+    }
+    
+
+    // ----------------------------------------------------- ActionHook Methods
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @Override
+    public void actionInternal(ActionCode actionCode, Object param) {
+
+        long socketRef = socket.getSocket().longValue();
+        
+        if (actionCode == ActionCode.CLOSE) {
+            // Close
+
+            // End the processing of the current request, and stop any further
+            // transactions with the client
+
+            comet = false;
+            try {
+                outputBuffer.endRequest();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+            }
+
+        } else if (actionCode == ActionCode.REQ_HOST_ADDR_ATTRIBUTE) {
+
+            // Get remote host address
+            if (remoteAddr == null && (socketRef != 0)) {
+                try {
+                    long sa = Address.get(Socket.APR_REMOTE, socketRef);
+                    remoteAddr = Address.getip(sa);
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.info"), e);
+                }
+            }
+            request.remoteAddr().setString(remoteAddr);
+
+        } else if (actionCode == ActionCode.REQ_LOCAL_NAME_ATTRIBUTE) {
+
+            // Get local host name
+            if (localName == null && (socketRef != 0)) {
+                try {
+                    long sa = Address.get(Socket.APR_LOCAL, socketRef);
+                    localName = Address.getnameinfo(sa, 0);
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.info"), e);
+                }
+            }
+            request.localName().setString(localName);
+
+        } else if (actionCode == ActionCode.REQ_HOST_ATTRIBUTE) {
+
+            // Get remote host name
+            if (remoteHost == null && (socketRef != 0)) {
+                try {
+                    long sa = Address.get(Socket.APR_REMOTE, socketRef);
+                    remoteHost = Address.getnameinfo(sa, 0);
+                    if (remoteHost == null) {
+                        remoteHost = Address.getip(sa);
+                    }
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.info"), e);
+                }
+            }
+            request.remoteHost().setString(remoteHost);
+
+        } else if (actionCode == ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE) {
+
+            // Get local host address
+            if (localAddr == null && (socketRef != 0)) {
+                try {
+                    long sa = Address.get(Socket.APR_LOCAL, socketRef);
+                    localAddr = Address.getip(sa);
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.info"), e);
+                }
+            }
+
+            request.localAddr().setString(localAddr);
+
+        } else if (actionCode == ActionCode.REQ_REMOTEPORT_ATTRIBUTE) {
+
+            // Get remote port
+            if (remotePort == -1 && (socketRef != 0)) {
+                try {
+                    long sa = Address.get(Socket.APR_REMOTE, socketRef);
+                    Sockaddr addr = Address.getInfo(sa);
+                    remotePort = addr.port;
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.info"), e);
+                }
+            }
+            request.setRemotePort(remotePort);
+
+        } else if (actionCode == ActionCode.REQ_LOCALPORT_ATTRIBUTE) {
+
+            // Get local port
+            if (localPort == -1 && (socketRef != 0)) {
+                try {
+                    long sa = Address.get(Socket.APR_LOCAL, socketRef);
+                    Sockaddr addr = Address.getInfo(sa);
+                    localPort = addr.port;
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.info"), e);
+                }
+            }
+            request.setLocalPort(localPort);
+
+        } else if (actionCode == ActionCode.REQ_SSL_ATTRIBUTE ) {
+
+            if (ssl && (socketRef != 0)) {
+                try {
+                    // Cipher suite
+                    Object sslO = SSLSocket.getInfoS(socketRef, SSL.SSL_INFO_CIPHER);
+                    if (sslO != null) {
+                        request.setAttribute(SSLSupport.CIPHER_SUITE_KEY, sslO);
+                    }
+                    // Get client certificate and the certificate chain if present
+                    // certLength == -1 indicates an error
+                    int certLength = SSLSocket.getInfoI(socketRef, SSL.SSL_INFO_CLIENT_CERT_CHAIN);
+                    byte[] clientCert = SSLSocket.getInfoB(socketRef, SSL.SSL_INFO_CLIENT_CERT);
+                    X509Certificate[] certs = null;
+                    if (clientCert != null  && certLength > -1) {
+                        certs = new X509Certificate[certLength + 1];
+                        CertificateFactory cf;
+                        if (clientCertProvider == null) {
+                            cf = CertificateFactory.getInstance("X.509"); 
+                        } else {
+                            cf = CertificateFactory.getInstance("X.509",
+                                    clientCertProvider); 
+                        }
+                        certs[0] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(clientCert));
+                        for (int i = 0; i < certLength; i++) {
+                            byte[] data = SSLSocket.getInfoB(socketRef, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);
+                            certs[i+1] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data));
+                        }
+                    }
+                    if (certs != null) {
+                        request.setAttribute(SSLSupport.CERTIFICATE_KEY, certs);
+                    }
+                    // User key size
+                    sslO = Integer.valueOf(SSLSocket.getInfoI(socketRef,
+                            SSL.SSL_INFO_CIPHER_USEKEYSIZE));
+                    request.setAttribute(SSLSupport.KEY_SIZE_KEY, sslO);
+
+                    // SSL session ID
+                    sslO = SSLSocket.getInfoS(socketRef, SSL.SSL_INFO_SESSION_ID);
+                    if (sslO != null) {
+                        request.setAttribute(SSLSupport.SESSION_ID_KEY, sslO);
+                    }
+                    //TODO provide a hook to enable the SSL session to be
+                    // invalidated. Set AprEndpoint.SESSION_MGR req attr
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.ssl"), e);
+                }
+            }
+
+        } else if (actionCode == ActionCode.REQ_SSL_CERTIFICATE) {
+
+            if (ssl && (socketRef != 0)) {
+                // Consume and buffer the request body, so that it does not
+                // interfere with the client's handshake messages
+                InputFilter[] inputFilters = inputBuffer.getFilters();
+                ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER]).setLimit(maxSavePostSize);
+                inputBuffer.addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]);
+                try {
+                    // Configure connection to require a certificate
+                    SSLSocket.setVerify(socketRef, SSL.SSL_CVERIFY_REQUIRE,
+                            endpoint.getSSLVerifyDepth());
+                    // Renegotiate certificates
+                    if (SSLSocket.renegotiate(socketRef) == 0) {
+                        // Don't look for certs unless we know renegotiation worked.
+                        // Get client certificate and the certificate chain if present
+                        // certLength == -1 indicates an error 
+                        int certLength = SSLSocket.getInfoI(socketRef,SSL.SSL_INFO_CLIENT_CERT_CHAIN);
+                        byte[] clientCert = SSLSocket.getInfoB(socketRef, SSL.SSL_INFO_CLIENT_CERT);
+                        X509Certificate[] certs = null;
+                        if (clientCert != null && certLength > -1) {
+                            certs = new X509Certificate[certLength + 1];
+                            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+                            certs[0] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(clientCert));
+                            for (int i = 0; i < certLength; i++) {
+                                byte[] data = SSLSocket.getInfoB(socketRef, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);
+                                certs[i+1] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data));
+                            }
+                        }
+                        if (certs != null) {
+                            request.setAttribute(SSLSupport.CERTIFICATE_KEY, certs);
+                        }
+                    }
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.ssl"), e);
+                }
+            }
+
+        } else if (actionCode == ActionCode.AVAILABLE) {
+            request.setAvailable(inputBuffer.available());
+        } else if (actionCode == ActionCode.COMET_BEGIN) {
+            comet = true;
+        } else if (actionCode == ActionCode.COMET_END) {
+            comet = false;
+        } else if (actionCode == ActionCode.COMET_CLOSE) {
+            endpoint.processSocketAsync(this.socket, SocketStatus.OPEN);
+        } else if (actionCode == ActionCode.COMET_SETTIMEOUT) {
+            //no op
+        } else if (actionCode == ActionCode.ASYNC_COMPLETE) {
+            if (asyncStateMachine.asyncComplete()) {
+                endpoint.processSocketAsync(this.socket, SocketStatus.OPEN);
+            }
+        } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
+            if (param==null) return;
+            long timeout = ((Long)param).longValue();
+            socket.setTimeout(timeout);
+        } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
+            if (asyncStateMachine.asyncDispatch()) {
+                endpoint.processSocketAsync(this.socket, SocketStatus.OPEN);
+            }
+        }
+        
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * After reading the request headers, we have to setup the request filters.
+     */
+    protected void prepareRequest() {
+
+        http11 = true;
+        http09 = false;
+        contentDelimitation = false;
+        expectation = false;
+        sendfileData = null;
+        if (ssl) {
+            request.scheme().setString("https");
+        }
+        MessageBytes protocolMB = request.protocol();
+        if (protocolMB.equals(Constants.HTTP_11)) {
+            http11 = true;
+            protocolMB.setString(Constants.HTTP_11);
+        } else if (protocolMB.equals(Constants.HTTP_10)) {
+            http11 = false;
+            keepAlive = false;
+            protocolMB.setString(Constants.HTTP_10);
+        } else if (protocolMB.equals("")) {
+            // HTTP/0.9
+            http09 = true;
+            http11 = false;
+            keepAlive = false;
+        } else {
+            // Unsupported protocol
+            http11 = false;
+            error = true;
+            // Send 505; Unsupported HTTP version
+            response.setStatus(505);
+            adapter.log(request, response, 0);
+        }
+
+        MessageBytes methodMB = request.method();
+        if (methodMB.equals(Constants.GET)) {
+            methodMB.setString(Constants.GET);
+        } else if (methodMB.equals(Constants.POST)) {
+            methodMB.setString(Constants.POST);
+        }
+
+        MimeHeaders headers = request.getMimeHeaders();
+
+        // Check connection header
+        MessageBytes connectionValueMB = headers.getValue("connection");
+        if (connectionValueMB != null) {
+            ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
+            if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
+                keepAlive = false;
+            } else if (findBytes(connectionValueBC,
+                                 Constants.KEEPALIVE_BYTES) != -1) {
+                keepAlive = true;
+            }
+        }
+
+        MessageBytes expectMB = null;
+        if (http11)
+            expectMB = headers.getValue("expect");
+        if ((expectMB != null)
+            && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
+            inputBuffer.setSwallowInput(false);
+            expectation = true;
+        }
+
+        // Check user-agent header
+        if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
+            MessageBytes userAgentValueMB = headers.getValue("user-agent");
+            // Check in the restricted list, and adjust the http11
+            // and keepAlive flags accordingly
+            if(userAgentValueMB != null) {
+                String userAgentValue = userAgentValueMB.toString();
+                if (restrictedUserAgents != null &&
+                        restrictedUserAgents.matcher(userAgentValue).matches()) {
+                    http11 = false;
+                    keepAlive = false;
+                }
+            }
+        }
+
+        // Check for a full URI (including protocol://host:port/)
+        ByteChunk uriBC = request.requestURI().getByteChunk();
+        if (uriBC.startsWithIgnoreCase("http", 0)) {
+
+            int pos = uriBC.indexOf("://", 0, 3, 4);
+            int uriBCStart = uriBC.getStart();
+            int slashPos = -1;
+            if (pos != -1) {
+                byte[] uriB = uriBC.getBytes();
+                slashPos = uriBC.indexOf('/', pos + 3);
+                if (slashPos == -1) {
+                    slashPos = uriBC.getLength();
+                    // Set URI as "/"
+                    request.requestURI().setBytes
+                        (uriB, uriBCStart + pos + 1, 1);
+                } else {
+                    request.requestURI().setBytes
+                        (uriB, uriBCStart + slashPos,
+                         uriBC.getLength() - slashPos);
+                }
+                MessageBytes hostMB = headers.setValue("host");
+                hostMB.setBytes(uriB, uriBCStart + pos + 3,
+                                slashPos - pos - 3);
+            }
+
+        }
+
+        // Input filter setup
+        InputFilter[] inputFilters = inputBuffer.getFilters();
+
+        // Parse transfer-encoding header
+        MessageBytes transferEncodingValueMB = null;
+        if (http11)
+            transferEncodingValueMB = headers.getValue("transfer-encoding");
+        if (transferEncodingValueMB != null) {
+            String transferEncodingValue = transferEncodingValueMB.toString();
+            // Parse the comma separated list. "identity" codings are ignored
+            int startPos = 0;
+            int commaPos = transferEncodingValue.indexOf(',');
+            String encodingName = null;
+            while (commaPos != -1) {
+                encodingName = transferEncodingValue.substring
+                    (startPos, commaPos).toLowerCase(Locale.ENGLISH).trim();
+                if (!addInputFilter(inputFilters, encodingName)) {
+                    // Unsupported transfer encoding
+                    error = true;
+                    // 501 - Unimplemented
+                    response.setStatus(501);
+                    adapter.log(request, response, 0);
+                }
+                startPos = commaPos + 1;
+                commaPos = transferEncodingValue.indexOf(',', startPos);
+            }
+            encodingName = transferEncodingValue.substring(startPos)
+                .toLowerCase(Locale.ENGLISH).trim();
+            if (!addInputFilter(inputFilters, encodingName)) {
+                // Unsupported transfer encoding
+                error = true;
+                // 501 - Unimplemented
+                response.setStatus(501);
+                adapter.log(request, response, 0);
+            }
+        }
+
+        // Parse content-length header
+        long contentLength = request.getContentLengthLong();
+        if (contentLength >= 0 && !contentDelimitation) {
+            inputBuffer.addActiveFilter
+                (inputFilters[Constants.IDENTITY_FILTER]);
+            contentDelimitation = true;
+        }
+
+        MessageBytes valueMB = headers.getValue("host");
+
+        // Check host header
+        if (http11 && (valueMB == null)) {
+            error = true;
+            // 400 - Bad request
+            response.setStatus(400);
+            adapter.log(request, response, 0);
+        }
+
+        parseHost(valueMB);
+
+        if (!contentDelimitation) {
+            // If there's no content length 
+            // (broken HTTP/1.0 or HTTP/1.1), assume
+            // the client is not broken and didn't send a body
+            inputBuffer.addActiveFilter
+                    (inputFilters[Constants.VOID_FILTER]);
+            contentDelimitation = true;
+        }
+
+        // Advertise sendfile support through a request attribute
+        if (endpoint.getUseSendfile()) {
+            request.setAttribute("org.apache.tomcat.sendfile.support", Boolean.TRUE);
+        }
+        // Advertise comet support through a request attribute
+        request.setAttribute("org.apache.tomcat.comet.support", Boolean.TRUE);
+        
+    }
+
+
+    /**
+     * Parse host.
+     */
+    public void parseHost(MessageBytes valueMB) {
+
+        if (valueMB == null || valueMB.isNull()) {
+            // HTTP/1.0
+            // Default is what the socket tells us. Overridden if a host is
+            // found/parsed
+            request.setServerPort(endpoint.getPort());
+            return;
+        }
+
+        ByteChunk valueBC = valueMB.getByteChunk();
+        byte[] valueB = valueBC.getBytes();
+        int valueL = valueBC.getLength();
+        int valueS = valueBC.getStart();
+        int colonPos = -1;
+        if (hostNameC.length < valueL) {
+            hostNameC = new char[valueL];
+        }
+
+        boolean ipv6 = (valueB[valueS] == '[');
+        boolean bracketClosed = false;
+        for (int i = 0; i < valueL; i++) {
+            char b = (char) valueB[i + valueS];
+            hostNameC[i] = b;
+            if (b == ']') {
+                bracketClosed = true;
+            } else if (b == ':') {
+                if (!ipv6 || bracketClosed) {
+                    colonPos = i;
+                    break;
+                }
+            }
+        }
+
+        if (colonPos < 0) {
+            if (!ssl) {
+                // 80 - Default HTTP port
+                request.setServerPort(80);
+            } else {
+                // 443 - Default HTTPS port
+                request.setServerPort(443);
+            }
+            request.serverName().setChars(hostNameC, 0, valueL);
+        } else {
+
+            request.serverName().setChars(hostNameC, 0, colonPos);
+
+            int port = 0;
+            int mult = 1;
+            for (int i = valueL - 1; i > colonPos; i--) {
+                int charValue = HexUtils.getDec(valueB[i + valueS]);
+                if (charValue == -1) {
+                    // Invalid character
+                    error = true;
+                    // 400 - Bad request
+                    response.setStatus(400);
+                    adapter.log(request, response, 0);
+                    break;
+                }
+                port = port + (charValue * mult);
+                mult = 10 * mult;
+            }
+            request.setServerPort(port);
+
+        }
+
+    }
+
+
+    @Override
+    protected boolean prepareSendfile(OutputFilter[] outputFilters) {
+        String fileName = (String) request.getAttribute("org.apache.tomcat.sendfile.filename");
+        if (fileName != null) {
+            // No entity body sent here
+            outputBuffer.addActiveFilter
+                (outputFilters[Constants.VOID_FILTER]);
+            contentDelimitation = true;
+            sendfileData = new AprEndpoint.SendfileData();
+            sendfileData.fileName = fileName;
+            sendfileData.start = 
+                ((Long) request.getAttribute("org.apache.tomcat.sendfile.start")).longValue();
+            sendfileData.end = 
+                ((Long) request.getAttribute("org.apache.tomcat.sendfile.end")).longValue();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected AbstractInputBuffer getInputBuffer() {
+        return inputBuffer;
+    }
+
+    @Override
+    protected AbstractOutputBuffer getOutputBuffer() {
+        return outputBuffer;
+    }
+    
+    @Override
+    public Executor getExecutor() {
+        return endpoint.getExecutor();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11AprProtocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11AprProtocol.java
new file mode 100644
index 0000000..a112b9d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11AprProtocol.java
@@ -0,0 +1,486 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.ObjectName;
+
+import org.apache.coyote.RequestGroupInfo;
+import org.apache.coyote.RequestInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.AprEndpoint.Handler;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class Http11AprProtocol extends AbstractHttp11Protocol {
+
+    private static final Log log = LogFactory.getLog(Http11AprProtocol.class);
+
+
+    @Override
+    protected Log getLog() { return log; }
+
+
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    public Http11AprProtocol() {
+        endpoint = new AprEndpoint();
+        cHandler = new Http11ConnectionHandler(this);
+        ((AprEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+        setProcessorCache(-1);
+    }
+
+    private final Http11ConnectionHandler cHandler;
+
+    public boolean getUseSendfile() { return ((AprEndpoint)endpoint).getUseSendfile(); }
+    public void setUseSendfile(boolean useSendfile) { ((AprEndpoint)endpoint).setUseSendfile(useSendfile); }
+
+    public int getPollTime() { return ((AprEndpoint)endpoint).getPollTime(); }
+    public void setPollTime(int pollTime) { ((AprEndpoint)endpoint).setPollTime(pollTime); }
+
+    public void setPollerSize(int pollerSize) { ((AprEndpoint)endpoint).setPollerSize(pollerSize); }
+    public int getPollerSize() { return ((AprEndpoint)endpoint).getPollerSize(); }
+
+    public void setPollerThreadCount(int pollerThreadCount) { ((AprEndpoint)endpoint).setPollerThreadCount(pollerThreadCount); }
+    public int getPollerThreadCount() { return ((AprEndpoint)endpoint).getPollerThreadCount(); }
+    
+    public int getSendfileSize() { return ((AprEndpoint)endpoint).getSendfileSize(); }
+    public void setSendfileSize(int sendfileSize) { ((AprEndpoint)endpoint).setSendfileSize(sendfileSize); }
+    
+    public void setSendfileThreadCount(int sendfileThreadCount) { ((AprEndpoint)endpoint).setSendfileThreadCount(sendfileThreadCount); }
+    public int getSendfileThreadCount() { return ((AprEndpoint)endpoint).getSendfileThreadCount(); }
+
+    public boolean getDeferAccept() { return ((AprEndpoint)endpoint).getDeferAccept(); }
+    public void setDeferAccept(boolean deferAccept) { ((AprEndpoint)endpoint).setDeferAccept(deferAccept); }
+
+    // --------------------  SSL related properties --------------------
+
+    /**
+     * SSL protocol.
+     */
+    public String getSSLProtocol() { return ((AprEndpoint)endpoint).getSSLProtocol(); }
+    public void setSSLProtocol(String SSLProtocol) { ((AprEndpoint)endpoint).setSSLProtocol(SSLProtocol); }
+
+
+    /**
+     * SSL password (if a cert is encrypted, and no password has been provided, a callback
+     * will ask for a password).
+     */
+    public String getSSLPassword() { return ((AprEndpoint)endpoint).getSSLPassword(); }
+    public void setSSLPassword(String SSLPassword) { ((AprEndpoint)endpoint).setSSLPassword(SSLPassword); }
+
+
+    /**
+     * SSL cipher suite.
+     */
+    public String getSSLCipherSuite() { return ((AprEndpoint)endpoint).getSSLCipherSuite(); }
+    public void setSSLCipherSuite(String SSLCipherSuite) { ((AprEndpoint)endpoint).setSSLCipherSuite(SSLCipherSuite); }
+
+
+    /**
+     * SSL certificate file.
+     */
+    public String getSSLCertificateFile() { return ((AprEndpoint)endpoint).getSSLCertificateFile(); }
+    public void setSSLCertificateFile(String SSLCertificateFile) { ((AprEndpoint)endpoint).setSSLCertificateFile(SSLCertificateFile); }
+
+
+    /**
+     * SSL certificate key file.
+     */
+    public String getSSLCertificateKeyFile() { return ((AprEndpoint)endpoint).getSSLCertificateKeyFile(); }
+    public void setSSLCertificateKeyFile(String SSLCertificateKeyFile) { ((AprEndpoint)endpoint).setSSLCertificateKeyFile(SSLCertificateKeyFile); }
+
+
+    /**
+     * SSL certificate chain file.
+     */
+    public String getSSLCertificateChainFile() { return ((AprEndpoint)endpoint).getSSLCertificateChainFile(); }
+    public void setSSLCertificateChainFile(String SSLCertificateChainFile) { ((AprEndpoint)endpoint).setSSLCertificateChainFile(SSLCertificateChainFile); }
+
+
+    /**
+     * SSL CA certificate path.
+     */
+    public String getSSLCACertificatePath() { return ((AprEndpoint)endpoint).getSSLCACertificatePath(); }
+    public void setSSLCACertificatePath(String SSLCACertificatePath) { ((AprEndpoint)endpoint).setSSLCACertificatePath(SSLCACertificatePath); }
+
+
+    /**
+     * SSL CA certificate file.
+     */
+    public String getSSLCACertificateFile() { return ((AprEndpoint)endpoint).getSSLCACertificateFile(); }
+    public void setSSLCACertificateFile(String SSLCACertificateFile) { ((AprEndpoint)endpoint).setSSLCACertificateFile(SSLCACertificateFile); }
+
+
+    /**
+     * SSL CA revocation path.
+     */
+    public String getSSLCARevocationPath() { return ((AprEndpoint)endpoint).getSSLCARevocationPath(); }
+    public void setSSLCARevocationPath(String SSLCARevocationPath) { ((AprEndpoint)endpoint).setSSLCARevocationPath(SSLCARevocationPath); }
+
+
+    /**
+     * SSL CA revocation file.
+     */
+    public String getSSLCARevocationFile() { return ((AprEndpoint)endpoint).getSSLCARevocationFile(); }
+    public void setSSLCARevocationFile(String SSLCARevocationFile) { ((AprEndpoint)endpoint).setSSLCARevocationFile(SSLCARevocationFile); }
+
+
+    /**
+     * SSL verify client.
+     */
+    public String getSSLVerifyClient() { return ((AprEndpoint)endpoint).getSSLVerifyClient(); }
+    public void setSSLVerifyClient(String SSLVerifyClient) { ((AprEndpoint)endpoint).setSSLVerifyClient(SSLVerifyClient); }
+
+
+    /**
+     * SSL verify depth.
+     */
+    public int getSSLVerifyDepth() { return ((AprEndpoint)endpoint).getSSLVerifyDepth(); }
+    public void setSSLVerifyDepth(int SSLVerifyDepth) { ((AprEndpoint)endpoint).setSSLVerifyDepth(SSLVerifyDepth); }
+    
+    // ----------------------------------------------------- JMX related methods
+
+    @Override
+    protected String getNamePrefix() {
+        return ("http-apr");
+    }
+
+
+    // --------------------  Connection handler --------------------
+
+    static class Http11ConnectionHandler implements Handler {
+        
+        protected Http11AprProtocol proto;
+        protected AtomicLong registerCount = new AtomicLong(0);
+        protected RequestGroupInfo global = new RequestGroupInfo();
+        
+        protected ConcurrentHashMap<Long, Http11AprProcessor> connections =
+            new ConcurrentHashMap<Long, Http11AprProcessor>();
+
+        protected ConcurrentLinkedQueue<Http11AprProcessor> recycledProcessors = 
+            new ConcurrentLinkedQueue<Http11AprProcessor>() {
+            private static final long serialVersionUID = 1L;
+            protected AtomicInteger size = new AtomicInteger(0);
+            @Override
+            public boolean offer(Http11AprProcessor processor) {
+                boolean offer = (proto.getProcessorCache() == -1) ? true : (size.get() < proto.getProcessorCache());
+                //avoid over growing our cache or add after we have stopped
+                boolean result = false;
+                if ( offer ) {
+                    result = super.offer(processor);
+                    if ( result ) {
+                        size.incrementAndGet();
+                    }
+                }
+                if (!result) unregister(processor);
+                return result;
+            }
+            
+            @Override
+            public Http11AprProcessor poll() {
+                Http11AprProcessor result = super.poll();
+                if ( result != null ) {
+                    size.decrementAndGet();
+                }
+                return result;
+            }
+            
+            @Override
+            public void clear() {
+                Http11AprProcessor next = poll();
+                while ( next != null ) {
+                    unregister(next);
+                    next = poll();
+                }
+                super.clear();
+                size.set(0);
+            }
+        };
+
+
+        Http11ConnectionHandler(Http11AprProtocol proto) {
+            this.proto = proto;
+        }
+
+        @Override
+        public Object getGlobal() {
+            return global;
+        }
+        
+        @Override
+        public void recycle() {
+            recycledProcessors.clear();
+        }
+        
+        @Override
+        public SocketState event(SocketWrapper<Long> socket, SocketStatus status) {
+            Http11AprProcessor processor = connections.get(socket.getSocket());
+            
+            SocketState state = SocketState.CLOSED; 
+            if (processor != null) {
+                if (processor.comet) {
+                    // Call the appropriate event
+                    try {
+                        state = processor.event(status);
+                    } catch (java.net.SocketException e) {
+                        // SocketExceptions are normal
+                        Http11AprProtocol.log.debug(sm.getString(
+                                "http11protocol.proto.socketexception.debug"),
+                                e);
+                    } catch (java.io.IOException e) {
+                        // IOExceptions are normal
+                        Http11AprProtocol.log.debug(sm.getString(
+                                "http11protocol.proto.ioexception.debug"), e);
+                    }
+                    // Future developers: if you discover any other
+                    // rare-but-nonfatal exceptions, catch them here, and log as
+                    // above.
+                    catch (Throwable e) {
+                        ExceptionUtils.handleThrowable(e);
+                        // any other exception or error is odd. Here we log it
+                        // with "ERROR" level, so it will show up even on
+                        // less-than-verbose logs.
+                        Http11AprProtocol.log.error(sm.getString(
+                                "http11protocol.proto.error"), e);
+                    } finally {
+                        if (state != SocketState.LONG) {
+                            connections.remove(socket.getSocket());
+                            socket.setAsync(false);
+                            processor.recycle();
+                            recycledProcessors.offer(processor);
+                            if (state == SocketState.OPEN) {
+                                ((AprEndpoint)proto.endpoint).getPoller().add(socket.getSocket().longValue());
+                            }
+                        } else {
+                            ((AprEndpoint)proto.endpoint).getCometPoller().add(socket.getSocket().longValue());
+                        }
+                    }
+                } else if (processor.isAsync()) {
+                    state = asyncDispatch(socket, status);
+                }
+            }
+            return state;
+        }
+        
+        @Override
+        public SocketState process(SocketWrapper<Long> socket) {
+            Http11AprProcessor processor = recycledProcessors.poll();
+            try {
+                if (processor == null) {
+                    processor = createProcessor();
+                }
+
+                SocketState state = processor.process(socket);
+                if (state == SocketState.LONG) {
+                    if (processor.isAsync()) {
+                        // Check if the post processing is going to change the state
+                        state = processor.asyncPostProcess();
+                    }
+                }
+                if (state == SocketState.LONG || state == SocketState.ASYNC_END) {
+                    // Need to make socket available for next processing cycle
+                    // but no need for the poller
+                    connections.put(socket.getSocket(), processor);
+                    if (processor.isAsync()) {
+                        socket.setAsync(true);
+                    } else if (processor.comet) {
+                        ((AprEndpoint) proto.endpoint).getCometPoller().add(
+                                socket.getSocket().longValue());
+                    }
+                } else {
+                    processor.recycle();
+                    recycledProcessors.offer(processor);
+                }
+                return state;
+
+            } catch (java.net.SocketException e) {
+                // SocketExceptions are normal
+                log.debug(sm.getString(
+                        "http11protocol.proto.socketexception.debug"), e);
+            } catch (java.io.IOException e) {
+                // IOExceptions are normal
+                log.debug(sm.getString(
+                        "http11protocol.proto.ioexception.debug"), e);
+            }
+            // Future developers: if you discover any other
+            // rare-but-nonfatal exceptions, catch them here, and log as
+            // above.
+            catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                // any other exception or error is odd. Here we log it
+                // with "ERROR" level, so it will show up even on
+                // less-than-verbose logs.
+                Http11AprProtocol.log.error(
+                        sm.getString("http11protocol.proto.error"), e);
+            }
+            processor.recycle();
+            recycledProcessors.offer(processor);
+            return SocketState.CLOSED;
+        }
+
+        @Override
+        public SocketState asyncDispatch(SocketWrapper<Long> socket, SocketStatus status) {
+            Http11AprProcessor processor = connections.get(socket.getSocket());
+            
+            SocketState state = SocketState.CLOSED; 
+            if (processor != null) {
+                // Call the appropriate event
+                try {
+                    state = processor.asyncDispatch(socket, status);
+                // Future developers: if you discover any rare-but-nonfatal
+                // exceptions, catch them here, and log as per {@link #event()}
+                // above.
+                } catch (Throwable e) {
+                    ExceptionUtils.handleThrowable(e);
+                    // any other exception or error is odd. Here we log it
+                    // with "ERROR" level, so it will show up even on
+                    // less-than-verbose logs.
+                    Http11AprProtocol.log.error
+                        (sm.getString("http11protocol.proto.error"), e);
+                } finally {
+                    if (state == SocketState.LONG && processor.isAsync()) {
+                        state = processor.asyncPostProcess();
+                    }
+                    if (state != SocketState.LONG && state != SocketState.ASYNC_END) {
+                        connections.remove(socket.getSocket());
+                        socket.setAsync(false);
+                        processor.recycle();
+                        recycledProcessors.offer(processor);
+                        if (state == SocketState.OPEN) {
+                            ((AprEndpoint)proto.endpoint).getPoller().add(socket.getSocket().longValue());
+                        }
+                    }
+                }
+            }
+            return state;
+        }
+
+        protected Http11AprProcessor createProcessor() {
+            Http11AprProcessor processor = new Http11AprProcessor(
+                    proto.getMaxHttpHeaderSize(), (AprEndpoint)proto.endpoint,
+                    proto.getMaxTrailerSize());
+            processor.setAdapter(proto.adapter);
+            processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
+            processor.setConnectionUploadTimeout(
+                    proto.getConnectionUploadTimeout());
+            processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
+            processor.setCompressionMinSize(proto.getCompressionMinSize());
+            processor.setCompression(proto.getCompression());
+            processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
+            processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
+            processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
+            processor.setSocketBuffer(proto.getSocketBuffer());
+            processor.setMaxSavePostSize(proto.getMaxSavePostSize());
+            processor.setServer(proto.getServer());
+            processor.setClientCertProvider(proto.getClientCertProvider());
+            register(processor);
+            return processor;
+        }
+        
+        protected void register(Http11AprProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        long count = registerCount.incrementAndGet();
+                        final RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(global);
+                        final ObjectName rpName = new ObjectName
+                            (proto.getDomain() + ":type=RequestProcessor,worker="
+                                + proto.getName() + ",name=HttpRequest" + count);
+                        if (log.isDebugEnabled()) {
+                            log.debug("Register " + rpName);
+                        }
+                        if (Constants.IS_SECURITY_ENABLED) {
+                            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                                @Override
+                                public Void run() {
+                                    try {
+                                        Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                                    } catch (Exception e) {
+                                        log.warn("Error registering request");
+                                    }
+                                    return null;
+                                }
+                            });
+                        } else {
+                            Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                        }
+                        rp.setRpName(rpName);
+                    } catch (Exception e) {
+                        log.warn("Error registering request");
+                    }
+                }
+            }
+        }
+
+        protected void unregister(Http11AprProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(null);
+                        ObjectName rpName = rp.getRpName();
+                        if (log.isDebugEnabled()) {
+                            log.debug("Unregister " + rpName);
+                        }
+                        Registry.getRegistry(null, null).unregisterComponent(rpName);
+                        rp.setRpName(null);
+                    } catch (Exception e) {
+                        log.warn("Error unregistering request", e);
+                    }
+                }
+            }
+        }
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11NioProcessor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11NioProcessor.java
new file mode 100644
index 0000000..e774b7f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11NioProcessor.java
@@ -0,0 +1,1025 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.nio.channels.SelectionKey;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Request;
+import org.apache.coyote.RequestInfo;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.filters.BufferedInputFilter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.Ascii;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SecureNioChannel;
+import org.apache.tomcat.util.net.SocketStatus;
+
+
+/**
+ * Processes HTTP requests.
+ *
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ */
+public class Http11NioProcessor extends AbstractHttp11Processor {
+
+    private static final Log log = LogFactory.getLog(Http11NioProcessor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+
+    /**
+     * SSL information.
+     */
+    protected SSLSupport sslSupport;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public Http11NioProcessor(int maxHttpHeaderSize, NioEndpoint endpoint,
+            int maxTrailerSize) {
+
+        this.endpoint = endpoint;
+
+        request = new Request();
+        inputBuffer = new InternalNioInputBuffer(request, maxHttpHeaderSize);
+        request.setInputBuffer(inputBuffer);
+
+        response = new Response();
+        response.setHook(this);
+        outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize);
+        response.setOutputBuffer(outputBuffer);
+        request.setResponse(response);
+
+        ssl = endpoint.isSSLEnabled();
+
+        initializeFilters(maxTrailerSize);
+
+        // Cause loading of HexUtils
+        HexUtils.load();
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+    /**
+     * Input.
+     */
+    protected InternalNioInputBuffer inputBuffer = null;
+
+
+    /**
+     * Output.
+     */
+    protected InternalNioOutputBuffer outputBuffer = null;
+
+
+    /**
+     * Sendfile data.
+     */
+    protected NioEndpoint.SendfileData sendfileData = null;
+
+    /**
+     * Comet used.
+     */
+    protected boolean comet = false;
+    
+    /**
+     * Closed flag, a Comet async thread can 
+     * signal for this Nio processor to be closed and recycled instead
+     * of waiting for a timeout.
+     * Closed by HttpServletResponse.getWriter().close()
+     */
+    protected boolean cometClose = false;
+    
+    /**
+     * SSL enabled ?
+     */
+    protected boolean ssl = false;
+
+
+    /**
+     * Socket associated with the current connection.
+     */
+    protected NioChannel socket = null;
+
+
+    /**
+     * Associated endpoint.
+     */
+    protected NioEndpoint endpoint;
+
+    
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Expose the endpoint.
+     */
+    @Override
+    protected AbstractEndpoint getEndpoint() {
+        return endpoint;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState event(SocketStatus status)
+        throws IOException {
+
+        long soTimeout = endpoint.getSoTimeout();
+        int keepAliveTimeout = endpoint.getKeepAliveTimeout();
+
+        RequestInfo rp = request.getRequestProcessor();
+        final NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            error = !adapter.event(request, response, status);
+            if ( !error ) {
+                if (attach != null) {
+                    attach.setComet(comet);
+                    if (comet) {
+                        Integer comettimeout = (Integer) request.getAttribute("org.apache.tomcat.comet.timeout");
+                        if (comettimeout != null) attach.setTimeout(comettimeout.longValue());
+                    } else {
+                        //reset the timeout
+                        if (keepAlive && keepAliveTimeout>0) {
+                            attach.setTimeout(keepAliveTimeout);
+                        } else {
+                            attach.setTimeout(soTimeout);
+                        }
+                    }
+
+                }
+            }
+        } catch (InterruptedIOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (error) {
+            return SocketState.CLOSED;
+        } else if (!comet) {
+            return (keepAlive)?SocketState.OPEN:SocketState.CLOSED;
+        } else {
+            return SocketState.LONG;
+        }
+    }
+    
+    
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState asyncDispatch(SocketStatus status)
+        throws IOException {
+
+        long soTimeout = endpoint.getSoTimeout();
+        int keepAliveTimeout = endpoint.getKeepAliveTimeout();
+
+        RequestInfo rp = request.getRequestProcessor();
+        final NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            error = !adapter.asyncDispatch(request, response, status);
+            if ( !error ) {
+                if (attach != null) {
+                    attach.setComet(comet);
+                    if (comet) {
+                        Integer comettimeout = (Integer) request.getAttribute("org.apache.tomcat.comet.timeout");
+                        if (comettimeout != null) attach.setTimeout(comettimeout.longValue());
+                    } else {
+                        if (asyncStateMachine.isAsyncDispatching()) {
+                            //reset the timeout
+                            if (keepAlive && keepAliveTimeout>0) {
+                                attach.setTimeout(keepAliveTimeout);
+                            } else {
+                                attach.setTimeout(soTimeout);
+                            }
+                        }
+                    }
+
+                }
+            }
+        } catch (InterruptedIOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (error) {
+            return SocketState.CLOSED;
+        } else if (!comet && !isAsync()) {
+            return (keepAlive)?SocketState.OPEN:SocketState.CLOSED;
+        } else {
+            return SocketState.LONG;
+        }
+    }
+
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState process(NioChannel socket)
+        throws IOException {
+        RequestInfo rp = request.getRequestProcessor();
+        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+        // Setting up the socket
+        this.socket = socket;
+        inputBuffer.setSocket(socket);
+        outputBuffer.setSocket(socket);
+        inputBuffer.setSelectorPool(endpoint.getSelectorPool());
+        outputBuffer.setSelectorPool(endpoint.getSelectorPool());
+
+        // Error flag
+        error = false;
+        keepAlive = true;
+        comet = false;
+        
+        long soTimeout = endpoint.getSoTimeout();
+        int keepAliveTimeout = endpoint.getKeepAliveTimeout();
+
+        boolean keptAlive = false;
+        boolean openSocket = false;
+        boolean readComplete = true;
+        final KeyAttachment ka = (KeyAttachment)socket.getAttachment(false);
+        
+        while (!error && keepAlive && !comet && !isAsync() && !endpoint.isPaused()) {
+            //always default to our soTimeout
+            ka.setTimeout(soTimeout);
+            // Parsing the request header
+            try {
+                if( !disableUploadTimeout && keptAlive && soTimeout > 0 ) {
+                    socket.getIOChannel().socket().setSoTimeout((int)soTimeout);
+                }
+                if (!inputBuffer.parseRequestLine(keptAlive)) {
+                    // Haven't finished reading the request so keep the socket
+                    // open
+                    openSocket = true;
+                    // Check to see if we have read any of the request line yet
+                    if (inputBuffer.getParsingRequestLinePhase()<2) {
+                        // No data read, OK to recycle the processor
+                        // Continue to use keep alive timeout
+                        if (keepAliveTimeout>0) ka.setTimeout(keepAliveTimeout);
+                    } else {
+                        // Started to read request line. Need to keep processor
+                        // associated with socket
+                        readComplete = false;
+                    }
+                    if (endpoint.isPaused()) {
+                        // 503 - Service unavailable
+                        response.setStatus(503);
+                        adapter.log(request, response, 0);
+                        error = true;
+                    } else {
+                        break;
+                    }
+                }
+                if (!endpoint.isPaused()) {
+                    keptAlive = true;
+                    if ( !inputBuffer.parseHeaders() ) {
+                        //we've read part of the request, don't recycle it
+                        //instead associate it with the socket
+                        openSocket = true;
+                        readComplete = false;
+                        break;
+                    }
+                    request.setStartTime(System.currentTimeMillis());
+                    if (!disableUploadTimeout) { //only for body, not for request headers
+                        socket.getIOChannel().socket().setSoTimeout(
+                                connectionUploadTimeout);
+                    }
+                }
+            } catch (IOException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("http11processor.header.parse"), e);
+                }
+                error = true;
+                break;
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("http11processor.header.parse"), t);
+                }
+                // 400 - Bad Request
+                response.setStatus(400);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            if (!error) {
+                // Setting up filters, and parse some request headers
+                rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+                try {
+                    prepareRequest();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("http11processor.request.prepare"), t);
+                    }
+                    // 400 - Internal Server Error
+                    response.setStatus(400);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+            
+            if (maxKeepAliveRequests == 1 )
+                keepAlive = false;
+            if (maxKeepAliveRequests > 0 && ka.decrementKeepAlive() <= 0)
+                keepAlive = false;
+
+            // Process the request in the adapter
+            if (!error) {
+                try {
+                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+                    adapter.service(request, response);
+                    // Handle when the response was committed before a serious
+                    // error occurred.  Throwing a ServletException should both
+                    // set the status to 500 and set the errorException.
+                    // If we fail here, then the response is likely already
+                    // committed, so we can't try and set headers.
+                    if(keepAlive && !error) { // Avoid checking twice.
+                        error = response.getErrorException() != null ||
+                                statusDropsConnection(response.getStatus());
+                    }
+                    // Comet support
+                    SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+                    if (key != null) {
+                        NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
+                        if (attach != null)  {
+                            attach.setComet(comet);
+                            if (comet) {
+                                Integer comettimeout = (Integer) request.getAttribute("org.apache.tomcat.comet.timeout");
+                                if (comettimeout != null) attach.setTimeout(comettimeout.longValue());
+                            }
+                        }
+                    }
+                } catch (InterruptedIOException e) {
+                    error = true;
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("http11processor.request.process"), t);
+                    // 500 - Internal Server Error
+                    response.setStatus(500);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+
+            // Finish the handling of the request
+            if (!comet && !isAsync()) {
+                // If we know we are closing the connection, don't drain input.
+                // This way uploading a 100GB file doesn't tie up the thread 
+                // if the servlet has rejected it.
+                if(error)
+                    inputBuffer.setSwallowInput(false);
+                endRequest();
+            }
+
+            // If there was an error, make sure the request is counted as
+            // and error, and update the statistics counter
+            if (error) {
+                response.setStatus(500);
+            }
+            request.updateCounters();
+
+            if (!comet && !isAsync()) {
+                // Next request
+                inputBuffer.nextRequest();
+                outputBuffer.nextRequest();
+            }
+            
+            // Do sendfile as needed: add socket to sendfile and end
+            if (sendfileData != null && !error) {
+                ka.setSendfileData(sendfileData);
+                sendfileData.keepAlive = keepAlive;
+                SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+                //do the first write on this thread, might as well
+                openSocket = socket.getPoller().processSendfile(key,ka,true,true);
+                break;
+            }
+
+
+            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+
+        }//while
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+        if (error || endpoint.isPaused()) {
+            return SocketState.CLOSED;
+        } else if (comet || isAsync()) {
+            return SocketState.LONG;
+        } else {
+            return (openSocket) ? (readComplete?SocketState.OPEN:SocketState.LONG) : SocketState.CLOSED;
+        }
+
+    }
+
+
+    @Override
+    public void recycleInternal() {
+        this.socket = null;
+        this.cometClose = false;
+        this.comet = false;
+        remoteAddr = null;
+        remoteHost = null;
+        localAddr = null;
+        localName = null;
+        remotePort = -1;
+        localPort = -1;
+    }
+
+
+    // ----------------------------------------------------- ActionHook Methods
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @Override
+    public void actionInternal(ActionCode actionCode, Object param) {
+
+        if (actionCode == ActionCode.CLOSE) {
+            // Close
+            // End the processing of the current request, and stop any further
+            // transactions with the client
+
+            comet = false;
+            cometClose = true;
+            SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+            if ( key != null ) {
+                NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
+                if ( attach!=null && attach.getComet()) {
+                    //if this is a comet connection
+                    //then execute the connection closure at the next selector loop
+                    //request.getAttributes().remove("org.apache.tomcat.comet.timeout");
+                    //attach.setTimeout(5000); //force a cleanup in 5 seconds
+                    //attach.setError(true); //this has caused concurrency errors
+                }
+            }
+
+            try {
+                outputBuffer.endRequest();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+            }
+
+        } else if (actionCode == ActionCode.REQ_HOST_ADDR_ATTRIBUTE) {
+
+            // Get remote host address
+            if ((remoteAddr == null) && (socket != null)) {
+                InetAddress inetAddr = socket.getIOChannel().socket().getInetAddress();
+                if (inetAddr != null) {
+                    remoteAddr = inetAddr.getHostAddress();
+                }
+            }
+            request.remoteAddr().setString(remoteAddr);
+
+        } else if (actionCode == ActionCode.REQ_LOCAL_NAME_ATTRIBUTE) {
+
+            // Get local host name
+            if ((localName == null) && (socket != null)) {
+                InetAddress inetAddr = socket.getIOChannel().socket().getLocalAddress();
+                if (inetAddr != null) {
+                    localName = inetAddr.getHostName();
+                }
+            }
+            request.localName().setString(localName);
+
+        } else if (actionCode == ActionCode.REQ_HOST_ATTRIBUTE) {
+
+            // Get remote host name
+            if ((remoteHost == null) && (socket != null)) {
+                InetAddress inetAddr = socket.getIOChannel().socket().getInetAddress();
+                if (inetAddr != null) {
+                    remoteHost = inetAddr.getHostName();
+                }
+                if(remoteHost == null) {
+                    if(remoteAddr != null) {
+                        remoteHost = remoteAddr;
+                    } else { // all we can do is punt
+                        request.remoteHost().recycle();
+                    }
+                }
+            }
+            request.remoteHost().setString(remoteHost);
+
+        } else if (actionCode == ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE) {
+
+            if (localAddr == null)
+               localAddr = socket.getIOChannel().socket().getLocalAddress().getHostAddress();
+
+            request.localAddr().setString(localAddr);
+
+        } else if (actionCode == ActionCode.REQ_REMOTEPORT_ATTRIBUTE) {
+
+            if ((remotePort == -1 ) && (socket !=null)) {
+                remotePort = socket.getIOChannel().socket().getPort();
+            }
+            request.setRemotePort(remotePort);
+
+        } else if (actionCode == ActionCode.REQ_LOCALPORT_ATTRIBUTE) {
+
+            if ((localPort == -1 ) && (socket !=null)) {
+                localPort = socket.getIOChannel().socket().getLocalPort();
+            }
+            request.setLocalPort(localPort);
+
+        } else if (actionCode == ActionCode.REQ_SSL_ATTRIBUTE ) {
+
+            try {
+                if (sslSupport != null) {
+                    Object sslO = sslSupport.getCipherSuite();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.CIPHER_SUITE_KEY, sslO);
+                    sslO = sslSupport.getPeerCertificateChain(false);
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.CERTIFICATE_KEY, sslO);
+                    sslO = sslSupport.getKeySize();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.KEY_SIZE_KEY, sslO);
+                    sslO = sslSupport.getSessionId();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.SESSION_ID_KEY, sslO);
+                    request.setAttribute(SSLSupport.SESSION_MGR, sslSupport);
+                }
+            } catch (Exception e) {
+                log.warn(sm.getString("http11processor.socket.ssl"), e);
+            }
+
+        } else if (actionCode == ActionCode.REQ_SSL_CERTIFICATE) {
+
+            if( sslSupport != null) {
+                /*
+                 * Consume and buffer the request body, so that it does not
+                 * interfere with the client's handshake messages
+                 */
+                InputFilter[] inputFilters = inputBuffer.getFilters();
+                ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
+                    .setLimit(maxSavePostSize);
+                inputBuffer.addActiveFilter
+                    (inputFilters[Constants.BUFFERED_FILTER]);
+                SecureNioChannel sslChannel = (SecureNioChannel) socket;
+                SSLEngine engine = sslChannel.getSslEngine();
+                if (!engine.getNeedClientAuth()) {
+                    // Need to re-negotiate SSL connection
+                    engine.setNeedClientAuth(true);
+                    try {
+                        sslChannel.rehandshake(endpoint.getSoTimeout());
+                        sslSupport =
+                            endpoint.getHandler().getSslImplementation().getSSLSupport(
+                                    engine.getSession());
+                    } catch (IOException ioe) {
+                        log.warn(sm.getString("http11processor.socket.sslreneg",ioe));
+                    }
+                }
+
+                try {
+                    // use force=false since re-negotiation is handled above
+                    // (and it is a NO-OP for NIO anyway)
+                    Object sslO = sslSupport.getPeerCertificateChain(false);
+                    if( sslO != null) {
+                        request.setAttribute
+                            (SSLSupport.CERTIFICATE_KEY, sslO);
+                    }
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.ssl"), e);
+                }
+            }
+
+        } else if (actionCode == ActionCode.AVAILABLE) {
+            request.setAvailable(inputBuffer.available());
+        } else if (actionCode == ActionCode.COMET_BEGIN) {
+            comet = true;
+        } else if (actionCode == ActionCode.COMET_END) {
+            comet = false;
+        }  else if (actionCode == ActionCode.COMET_CLOSE) {
+            if (socket==null || socket.getAttachment(false)==null) return;
+            NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+            attach.setCometOps(NioEndpoint.OP_CALLBACK);
+            //notify poller if not on a tomcat thread
+            RequestInfo rp = request.getRequestProcessor();
+            if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) //async handling
+                socket.getPoller().add(socket);
+        } else if (actionCode == ActionCode.COMET_SETTIMEOUT) {
+            if (param==null) return;
+            if (socket==null || socket.getAttachment(false)==null) return;
+            NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+            long timeout = ((Long)param).longValue();
+            //if we are not piggy backing on a worker thread, set the timeout
+            RequestInfo rp = request.getRequestProcessor();
+            if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) //async handling
+                attach.setTimeout(timeout);
+        } else if (actionCode == ActionCode.ASYNC_COMPLETE) {
+            if (asyncStateMachine.asyncComplete()) {
+                endpoint.processSocket(this.socket, SocketStatus.OPEN, true);
+            }
+        } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
+            if (param==null) return;
+            if (socket==null || socket.getAttachment(false)==null) return;
+            NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+            long timeout = ((Long)param).longValue();
+            //if we are not piggy backing on a worker thread, set the timeout
+            attach.setTimeout(timeout);
+        } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
+            if (asyncStateMachine.asyncDispatch()) {
+                endpoint.processSocket(this.socket, SocketStatus.OPEN, true);
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * After reading the request headers, we have to setup the request filters.
+     */
+    protected void prepareRequest() {
+
+        http11 = true;
+        http09 = false;
+        contentDelimitation = false;
+        expectation = false;
+        sendfileData = null;
+        if (ssl) {
+            request.scheme().setString("https");
+        }
+        MessageBytes protocolMB = request.protocol();
+        if (protocolMB.equals(Constants.HTTP_11)) {
+            http11 = true;
+            protocolMB.setString(Constants.HTTP_11);
+        } else if (protocolMB.equals(Constants.HTTP_10)) {
+            http11 = false;
+            keepAlive = false;
+            protocolMB.setString(Constants.HTTP_10);
+        } else if (protocolMB.equals("")) {
+            // HTTP/0.9
+            http09 = true;
+            http11 = false;
+            keepAlive = false;
+        } else {
+            // Unsupported protocol
+            http11 = false;
+            error = true;
+            // Send 505; Unsupported HTTP version
+            response.setStatus(505);
+            adapter.log(request, response, 0);
+        }
+
+        MessageBytes methodMB = request.method();
+        if (methodMB.equals(Constants.GET)) {
+            methodMB.setString(Constants.GET);
+        } else if (methodMB.equals(Constants.POST)) {
+            methodMB.setString(Constants.POST);
+        }
+
+        MimeHeaders headers = request.getMimeHeaders();
+
+        // Check connection header
+        MessageBytes connectionValueMB = headers.getValue("connection");
+        if (connectionValueMB != null) {
+            ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
+            if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
+                keepAlive = false;
+            } else if (findBytes(connectionValueBC,
+                                 Constants.KEEPALIVE_BYTES) != -1) {
+                keepAlive = true;
+            }
+        }
+
+        MessageBytes expectMB = null;
+        if (http11)
+            expectMB = headers.getValue("expect");
+        if ((expectMB != null)
+            && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
+            inputBuffer.setSwallowInput(false);
+            expectation = true;
+        }
+
+        // Check user-agent header
+        if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
+            MessageBytes userAgentValueMB = headers.getValue("user-agent");
+            // Check in the restricted list, and adjust the http11
+            // and keepAlive flags accordingly
+            if(userAgentValueMB != null) {
+                String userAgentValue = userAgentValueMB.toString();
+                if (restrictedUserAgents != null &&
+                        restrictedUserAgents.matcher(userAgentValue).matches()) {
+                    http11 = false;
+                    keepAlive = false;
+                }
+            }
+        }
+
+        // Check for a full URI (including protocol://host:port/)
+        ByteChunk uriBC = request.requestURI().getByteChunk();
+        if (uriBC.startsWithIgnoreCase("http", 0)) {
+
+            int pos = uriBC.indexOf("://", 0, 3, 4);
+            int uriBCStart = uriBC.getStart();
+            int slashPos = -1;
+            if (pos != -1) {
+                byte[] uriB = uriBC.getBytes();
+                slashPos = uriBC.indexOf('/', pos + 3);
+                if (slashPos == -1) {
+                    slashPos = uriBC.getLength();
+                    // Set URI as "/"
+                    request.requestURI().setBytes
+                        (uriB, uriBCStart + pos + 1, 1);
+                } else {
+                    request.requestURI().setBytes
+                        (uriB, uriBCStart + slashPos,
+                         uriBC.getLength() - slashPos);
+                }
+                MessageBytes hostMB = headers.setValue("host");
+                hostMB.setBytes(uriB, uriBCStart + pos + 3,
+                                slashPos - pos - 3);
+            }
+
+        }
+
+        // Input filter setup
+        InputFilter[] inputFilters = inputBuffer.getFilters();
+
+        // Parse transfer-encoding header
+        MessageBytes transferEncodingValueMB = null;
+        if (http11)
+            transferEncodingValueMB = headers.getValue("transfer-encoding");
+        if (transferEncodingValueMB != null) {
+            String transferEncodingValue = transferEncodingValueMB.toString();
+            // Parse the comma separated list. "identity" codings are ignored
+            int startPos = 0;
+            int commaPos = transferEncodingValue.indexOf(',');
+            String encodingName = null;
+            while (commaPos != -1) {
+                encodingName = transferEncodingValue.substring
+                    (startPos, commaPos).toLowerCase(Locale.ENGLISH).trim();
+                if (!addInputFilter(inputFilters, encodingName)) {
+                    // Unsupported transfer encoding
+                    error = true;
+                    // 501 - Unimplemented
+                    response.setStatus(501);
+                    adapter.log(request, response, 0);
+                }
+                startPos = commaPos + 1;
+                commaPos = transferEncodingValue.indexOf(',', startPos);
+            }
+            encodingName = transferEncodingValue.substring(startPos)
+                .toLowerCase(Locale.ENGLISH).trim();
+            if (!addInputFilter(inputFilters, encodingName)) {
+                // Unsupported transfer encoding
+                error = true;
+                // 501 - Unimplemented
+                response.setStatus(501);
+                adapter.log(request, response, 0);
+            }
+        }
+
+        // Parse content-length header
+        long contentLength = request.getContentLengthLong();
+        if (contentLength >= 0 && !contentDelimitation) {
+            inputBuffer.addActiveFilter
+                (inputFilters[Constants.IDENTITY_FILTER]);
+            contentDelimitation = true;
+        }
+
+        MessageBytes valueMB = headers.getValue("host");
+
+        // Check host header
+        if (http11 && (valueMB == null)) {
+            error = true;
+            // 400 - Bad request
+            response.setStatus(400);
+            adapter.log(request, response, 0);
+        }
+
+        parseHost(valueMB);
+
+        if (!contentDelimitation) {
+            // If there's no content length 
+            // (broken HTTP/1.0 or HTTP/1.1), assume
+            // the client is not broken and didn't send a body
+            inputBuffer.addActiveFilter
+                    (inputFilters[Constants.VOID_FILTER]);
+            contentDelimitation = true;
+        }
+
+        // Advertise sendfile support through a request attribute
+        if (endpoint.getUseSendfile()) 
+            request.setAttribute("org.apache.tomcat.sendfile.support", Boolean.TRUE);
+        // Advertise comet support through a request attribute
+        request.setAttribute("org.apache.tomcat.comet.support", Boolean.TRUE);
+        // Advertise comet timeout support
+        request.setAttribute("org.apache.tomcat.comet.timeout.support", Boolean.TRUE);
+
+    }
+
+
+    /**
+     * Parse host.
+     */
+    public void parseHost(MessageBytes valueMB) {
+
+        if (valueMB == null || valueMB.isNull()) {
+            // HTTP/1.0
+            // Default is what the socket tells us. Overridden if a host is
+            // found/parsed
+            request.setServerPort(endpoint.getPort());
+            return;
+        }
+
+        ByteChunk valueBC = valueMB.getByteChunk();
+        byte[] valueB = valueBC.getBytes();
+        int valueL = valueBC.getLength();
+        int valueS = valueBC.getStart();
+        int colonPos = -1;
+        if (hostNameC.length < valueL) {
+            hostNameC = new char[valueL];
+        }
+
+        boolean ipv6 = (valueB[valueS] == '[');
+        boolean bracketClosed = false;
+        for (int i = 0; i < valueL; i++) {
+            char b = (char) valueB[i + valueS];
+            hostNameC[i] = b;
+            if (b == ']') {
+                bracketClosed = true;
+            } else if (b == ':') {
+                if (!ipv6 || bracketClosed) {
+                    colonPos = i;
+                    break;
+                }
+            }
+        }
+
+        if (colonPos < 0) {
+            if (!ssl) {
+                // 80 - Default HTTP port
+                request.setServerPort(80);
+            } else {
+                // 443 - Default HTTPS port
+                request.setServerPort(443);
+            }
+            request.serverName().setChars(hostNameC, 0, valueL);
+        } else {
+
+            request.serverName().setChars(hostNameC, 0, colonPos);
+
+            int port = 0;
+            int mult = 1;
+            for (int i = valueL - 1; i > colonPos; i--) {
+                int charValue = HexUtils.getDec(valueB[i + valueS]);
+                if (charValue == -1) {
+                    // Invalid character
+                    error = true;
+                    // 400 - Bad request
+                    response.setStatus(400);
+                    adapter.log(request, response, 0);
+                    break;
+                }
+                port = port + (charValue * mult);
+                mult = 10 * mult;
+            }
+            request.setServerPort(port);
+
+        }
+
+    }
+
+    @Override
+    protected boolean prepareSendfile(OutputFilter[] outputFilters) {
+        String fileName = (String) request.getAttribute("org.apache.tomcat.sendfile.filename");
+        if (fileName != null) {
+            // No entity body sent here
+            outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
+            contentDelimitation = true;
+            sendfileData = new NioEndpoint.SendfileData();
+            sendfileData.fileName = fileName;
+            sendfileData.pos = ((Long) request.getAttribute("org.apache.tomcat.sendfile.start")).longValue();
+            sendfileData.length = ((Long) request.getAttribute("org.apache.tomcat.sendfile.end")).longValue() - sendfileData.pos;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Specialized utility method: find a sequence of lower case bytes inside
+     * a ByteChunk.
+     */
+    @Override
+    protected int findBytes(ByteChunk bc, byte[] b) {
+
+        byte first = b[0];
+        byte[] buff = bc.getBuffer();
+        int start = bc.getStart();
+        int end = bc.getEnd();
+
+        // Look for first char
+        int srcEnd = b.length;
+    
+        for (int i = start; i <= (end - srcEnd); i++) {
+            if (Ascii.toLower(buff[i]) != first) continue;
+            // found first char, now look for a match
+                int myPos = i+1;
+            for (int srcPos = 1; srcPos < srcEnd;) {
+                    if (Ascii.toLower(buff[myPos++]) != b[srcPos++])
+                break;
+                    if (srcPos == srcEnd) return i - start; // found it
+            }
+        }
+        return -1;
+
+    }
+
+    @Override
+    protected AbstractInputBuffer getInputBuffer() {
+        return inputBuffer;
+    }
+
+    @Override
+    protected AbstractOutputBuffer getOutputBuffer() {
+        return outputBuffer;
+    }
+
+    /**
+     * Set the SSL information for this HTTP connection.
+     */
+    public void setSslSupport(SSLSupport sslSupport) {
+        this.sslSupport = sslSupport;
+    }
+
+    @Override
+    public Executor getExecutor() {
+        return endpoint.getExecutor();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11NioProtocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11NioProtocol.java
new file mode 100644
index 0000000..0cd5d7f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11NioProtocol.java
@@ -0,0 +1,494 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.management.ObjectName;
+
+import org.apache.coyote.RequestGroupInfo;
+import org.apache.coyote.RequestInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioEndpoint.Handler;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SecureNioChannel;
+import org.apache.tomcat.util.net.SocketStatus;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ * @author Filip Hanik
+ */
+public class Http11NioProtocol extends AbstractHttp11JsseProtocol {
+    
+    private static final Log log = LogFactory.getLog(Http11NioProtocol.class);
+
+
+    @Override
+    protected Log getLog() { return log; }
+    
+
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    public Http11NioProtocol() {
+        endpoint=new NioEndpoint();
+        cHandler = new Http11ConnectionHandler(this);
+        ((NioEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+        setProcessorCache(200);
+    }
+
+
+    public NioEndpoint getEndpoint() {
+        return ((NioEndpoint)endpoint);
+    }
+
+
+    // -------------------- Properties--------------------
+    
+
+    private int socketCloseDelay=-1;
+    
+    private Http11ConnectionHandler cHandler;
+
+    // -------------------- Pool setup --------------------
+
+    public void setPollerThreadCount(int count) {
+        ((NioEndpoint)endpoint).setPollerThreadCount(count);
+    }
+    
+    public int getPollerThreadCount() {
+        return ((NioEndpoint)endpoint).getPollerThreadCount();
+    }
+    
+    public void setSelectorTimeout(long timeout) {
+        ((NioEndpoint)endpoint).setSelectorTimeout(timeout);
+    }
+    
+    public long getSelectorTimeout() {
+        return ((NioEndpoint)endpoint).getSelectorTimeout();
+    }
+    // *
+
+    
+    public void setAcceptorThreadPriority(int threadPriority) {
+        ((NioEndpoint)endpoint).setAcceptorThreadPriority(threadPriority);
+    }
+
+    public void setPollerThreadPriority(int threadPriority) {
+        ((NioEndpoint)endpoint).setPollerThreadPriority(threadPriority);
+    }
+
+    public int getAcceptorThreadPriority() {
+      return ((NioEndpoint)endpoint).getAcceptorThreadPriority();
+    }
+    
+    public int getPollerThreadPriority() {
+      return ((NioEndpoint)endpoint).getThreadPriority();
+    }
+    
+    
+    public boolean getUseSendfile() {
+        return ((NioEndpoint)endpoint).getUseSendfile();
+    }
+
+    public void setUseSendfile(boolean useSendfile) {
+        ((NioEndpoint)endpoint).setUseSendfile(useSendfile);
+    }
+    
+    // -------------------- Tcp setup --------------------
+    public int getSocketCloseDelay() {
+        return socketCloseDelay;
+    }
+
+    public void setSocketCloseDelay( int d ) {
+        socketCloseDelay=d;
+    }
+
+    public void setOomParachute(int oomParachute) {
+        ((NioEndpoint)endpoint).setOomParachute(oomParachute);
+    }
+
+    // ----------------------------------------------------- JMX related methods
+
+    @Override
+    protected String getNamePrefix() {
+        return ("http-nio");
+    }
+
+
+    // --------------------  Connection handler --------------------
+
+    static class Http11ConnectionHandler implements Handler {
+
+        protected Http11NioProtocol proto;
+        protected static int count = 0;
+        protected RequestGroupInfo global = new RequestGroupInfo();
+
+        protected ConcurrentHashMap<NioChannel, Http11NioProcessor> connections =
+            new ConcurrentHashMap<NioChannel, Http11NioProcessor>();
+
+        protected ConcurrentLinkedQueue<Http11NioProcessor> recycledProcessors = new ConcurrentLinkedQueue<Http11NioProcessor>() {
+            private static final long serialVersionUID = 1L;
+            protected AtomicInteger size = new AtomicInteger(0);
+            @Override
+            public boolean offer(Http11NioProcessor processor) {
+                boolean offer = proto.getProcessorCache()==-1?true:size.get() < proto.getProcessorCache();
+                //avoid over growing our cache or add after we have stopped
+                boolean result = false;
+                if ( offer ) {
+                    result = super.offer(processor);
+                    if ( result ) {
+                        size.incrementAndGet();
+                    }
+                }
+                if (!result) deregister(processor);
+                return result;
+            }
+            
+            @Override
+            public Http11NioProcessor poll() {
+                Http11NioProcessor result = super.poll();
+                if ( result != null ) {
+                    size.decrementAndGet();
+                }
+                return result;
+            }
+            
+            @Override
+            public void clear() {
+                Http11NioProcessor next = poll();
+                while ( next != null ) {
+                    deregister(next);
+                    next = poll();
+                }
+                super.clear();
+                size.set(0);
+            }
+        };
+
+        Http11ConnectionHandler(Http11NioProtocol proto) {
+            this.proto = proto;
+        }
+        
+        @Override
+        public Object getGlobal() {
+            return global;
+        }
+
+        @Override
+        public void recycle() {
+            recycledProcessors.clear();
+        }
+        
+        @Override
+        public SSLImplementation getSslImplementation() {
+            return proto.sslImplementation;
+        }
+
+        @Override
+        public void release(SocketChannel socket) {
+            if (log.isDebugEnabled()) 
+                log.debug("Iterating through our connections to release a socket channel:"+socket);
+            boolean released = false;
+            Iterator<java.util.Map.Entry<NioChannel, Http11NioProcessor>> it = connections.entrySet().iterator();
+            while (it.hasNext()) {
+                java.util.Map.Entry<NioChannel, Http11NioProcessor> entry = it.next();
+                if (entry.getKey().getIOChannel()==socket) {
+                    it.remove();
+                    Http11NioProcessor result = entry.getValue();
+                    result.recycle();
+                    deregister(result);
+                    released = true;
+                    break;
+                }
+            }
+            if (log.isDebugEnabled()) 
+                log.debug("Done iterating through our connections to release a socket channel:"+socket +" released:"+released);
+        }
+        
+        /**
+         * Use this only if the processor is not available, otherwise use
+         * {@link #release(NioChannel, Http11NioProcessor).
+         */
+        @Override
+        public void release(NioChannel socket) {
+            Http11NioProcessor processor = connections.remove(socket);
+            if (processor != null) {
+                processor.recycle();
+                recycledProcessors.offer(processor);
+            }
+        }
+
+
+        public void release(NioChannel socket, Http11NioProcessor processor) {
+            connections.remove(socket);
+            processor.recycle();
+            recycledProcessors.offer(processor);
+        }
+
+
+        @Override
+        public SocketState event(NioChannel socket, SocketStatus status) {
+            Http11NioProcessor processor = connections.get(socket);
+            NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+            att.setAsync(false); //no longer check for timeout
+            SocketState state = SocketState.CLOSED; 
+            if (processor != null) {
+                if (log.isDebugEnabled()) log.debug("Http11NioProcessor.error="+processor.error);
+                // Call the appropriate event
+                try {
+                    if (processor.comet) {
+                        state = processor.event(status);
+                    } else {
+                        state = processor.asyncDispatch(status);
+                    }
+                } catch (java.net.SocketException e) {
+                    // SocketExceptions are normal
+                    Http11NioProtocol.log.debug
+                        (sm.getString
+                            ("http11protocol.proto.socketexception.debug"), e);
+                } catch (java.io.IOException e) {
+                    // IOExceptions are normal
+                    Http11NioProtocol.log.debug
+                        (sm.getString
+                            ("http11protocol.proto.ioexception.debug"), e);
+                }
+                // Future developers: if you discover any other
+                // rare-but-nonfatal exceptions, catch them here, and log as
+                // above.
+                catch (Throwable e) {
+                    ExceptionUtils.handleThrowable(e);
+                    // any other exception or error is odd. Here we log it
+                    // with "ERROR" level, so it will show up even on
+                    // less-than-verbose logs.
+                    Http11NioProtocol.log.error
+                        (sm.getString("http11protocol.proto.error"), e);
+                } finally {
+                    if (processor.isAsync()) {
+                        state = processor.asyncPostProcess();
+                    }
+                    if (state == SocketState.OPEN || state == SocketState.CLOSED) {
+                        release(socket, processor);
+                        if (state == SocketState.OPEN) {
+                            socket.getPoller().add(socket);
+                        }
+                    } else if (state == SocketState.LONG) {
+                        if (processor.isAsync()) {
+                            att.setAsync(true); // Re-enable timeouts
+                        } else {
+                            // Comet
+                            if (log.isDebugEnabled()) log.debug("Keeping processor["+processor);
+                            // May receive more data from client
+                            SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+                            key.interestOps(SelectionKey.OP_READ);
+                            att.interestOps(SelectionKey.OP_READ);
+                        }
+                    } else {
+                        // state == SocketState.ASYNC_END
+                        // No further work required
+                    }
+                }
+            }
+            return state;
+        }
+
+        @Override
+        public SocketState process(NioChannel socket) {
+            Http11NioProcessor processor = connections.remove(socket);
+            try {
+                if (processor == null) {
+                    processor = recycledProcessors.poll();
+                }
+                if (processor == null) {
+                    processor = createProcessor();
+                }
+
+                if (proto.isSSLEnabled() &&
+                        (proto.sslImplementation != null)
+                        && (socket instanceof SecureNioChannel)) {
+                    SecureNioChannel ch = (SecureNioChannel)socket;
+                    processor.setSslSupport(
+                            proto.sslImplementation.getSSLSupport(
+                                    ch.getSslEngine().getSession()));
+                } else {
+                    processor.setSslSupport(null);
+                }
+
+                SocketState state = processor.process(socket);
+                if (state == SocketState.LONG) {
+                    // In the middle of processing a request/response. Keep the
+                    // socket associated with the processor.
+                    connections.put(socket, processor);
+                    
+                    if (processor.isAsync()) {
+                        NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+                        att.setAsync(true);
+                        // longPoll may change socket state (e.g. to trigger a
+                        // complete or dispatch)
+                        state = processor.asyncPostProcess();
+                    } else {
+                        // Either:
+                        //  - this is comet request
+                        //  - the request line/headers have not been completely
+                        //    read
+                        SelectionKey key = socket.getIOChannel().keyFor(
+                                socket.getPoller().getSelector());
+                        NioEndpoint.KeyAttachment att =
+                            (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+                        key.interestOps(SelectionKey.OP_READ);
+                        att.interestOps(SelectionKey.OP_READ);
+                    }
+                }
+                if (state == SocketState.LONG || state == SocketState.ASYNC_END) {
+                    // Already done all we need to do.
+                } else if (state == SocketState.OPEN){
+                    // In keep-alive but between requests. OK to recycle
+                    // processor. Continue to poll for the next request.
+                    release(socket, processor);
+                    socket.getPoller().add(socket);
+                } else {
+                    // Connection closed. OK to recycle the processor.
+                    release(socket, processor);
+                }
+                return state;
+
+            } catch (java.net.SocketException e) {
+                // SocketExceptions are normal
+                log.debug(sm.getString(
+                        "http11protocol.proto.socketexception.debug"), e);
+            } catch (java.io.IOException e) {
+                // IOExceptions are normal
+                log.debug(sm.getString(
+                        "http11protocol.proto.ioexception.debug"), e);
+            }
+            // Future developers: if you discover any other
+            // rare-but-nonfatal exceptions, catch them here, and log as
+            // above.
+            catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                // any other exception or error is odd. Here we log it
+                // with "ERROR" level, so it will show up even on
+                // less-than-verbose logs.
+                log.error(sm.getString("http11protocol.proto.error"), e);
+            }
+            release(socket, processor);
+            return SocketState.CLOSED;
+        }
+
+        public Http11NioProcessor createProcessor() {
+            Http11NioProcessor processor = new Http11NioProcessor(
+                    proto.getMaxHttpHeaderSize(), (NioEndpoint)proto.endpoint,
+                    proto.getMaxTrailerSize());
+            processor.setAdapter(proto.adapter);
+            processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
+            processor.setConnectionUploadTimeout(
+                    proto.getConnectionUploadTimeout());
+            processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
+            processor.setCompressionMinSize(proto.getCompressionMinSize());
+            processor.setCompression(proto.getCompression());
+            processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
+            processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
+            processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
+            processor.setSocketBuffer(proto.getSocketBuffer());
+            processor.setMaxSavePostSize(proto.getMaxSavePostSize());
+            processor.setServer(proto.getServer());
+            register(processor);
+            return processor;
+        }
+        AtomicInteger registerCount = new AtomicInteger(0);
+        public void register(Http11NioProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        registerCount.addAndGet(1);
+                        if (log.isDebugEnabled()) log.debug("Register ["+processor+"] count="+registerCount.get());
+                        final RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(global);
+                        final ObjectName rpName = new ObjectName
+                            (proto.getDomain() + ":type=RequestProcessor,worker="
+                             + proto.getName() + ",name=HttpRequest" + count++);
+                        if (Constants.IS_SECURITY_ENABLED) {
+                            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                                @Override
+                                public Void run() {
+                                    try {
+                                        Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                                    } catch (Exception e) {
+                                        log.warn("Error registering request");
+                                    }
+                                    return null;
+                                }
+                            });
+                        } else {
+                            Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                        }
+                        rp.setRpName(rpName);
+                    } catch (Exception e) {
+                        log.warn("Error registering request");
+                    }
+                }
+            }
+        }
+    
+        public void deregister(Http11NioProcessor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        registerCount.addAndGet(-1);
+                        if (log.isDebugEnabled()) log.debug("Deregister ["+processor+"] count="+registerCount.get());
+                        RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(null);
+                        ObjectName rpName = rp.getRpName();
+                        Registry.getRegistry(null, null).unregisterComponent(rpName);
+                        rp.setRpName(null);
+                    } catch (Exception e) {
+                        log.warn("Error unregistering request", e);
+                    }
+                }
+            }
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11Processor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11Processor.java
new file mode 100644
index 0000000..cc95597
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11Processor.java
@@ -0,0 +1,827 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Request;
+import org.apache.coyote.RequestInfo;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.filters.BufferedInputFilter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Processes HTTP requests.
+ *
+ * @author Remy Maucherat
+ * @author fhanik
+ */
+public class Http11Processor extends AbstractHttp11Processor {
+
+    private static final Log log = LogFactory.getLog(Http11Processor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+   // ------------------------------------------------------------ Constructor
+
+
+    public Http11Processor(int headerBufferSize, JIoEndpoint endpoint,
+            int maxTrailerSize) {
+
+        this.endpoint = endpoint;
+        
+        request = new Request();
+        inputBuffer = new InternalInputBuffer(request, headerBufferSize);
+        request.setInputBuffer(inputBuffer);
+
+        response = new Response();
+        response.setHook(this);
+        outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
+        response.setOutputBuffer(outputBuffer);
+        request.setResponse(response);
+
+        initializeFilters(maxTrailerSize);
+
+        // Cause loading of HexUtils
+        HexUtils.load();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Input.
+     */
+    protected InternalInputBuffer inputBuffer = null;
+
+
+    /**
+     * Output.
+     */
+    protected InternalOutputBuffer outputBuffer = null;
+
+
+    /**
+     * SSL information.
+     */
+    protected SSLSupport sslSupport;
+
+    
+    /**
+     * Socket associated with the current connection.
+     */
+    protected SocketWrapper<Socket> socket;
+
+
+    /**
+     * Associated endpoint.
+     */
+    protected JIoEndpoint endpoint;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Expose the endpoint.
+     */
+    @Override
+    protected AbstractEndpoint getEndpoint() {
+        return this.endpoint;
+    }
+
+    /**
+     * Set the SSL information for this HTTP connection.
+     */
+    public void setSSLSupport(SSLSupport sslSupport) {
+        this.sslSupport = sslSupport;
+    }
+
+
+    /**
+     * Process pipelined HTTP requests on the specified socket.
+     *
+     * @param socketWrapper Socket from which the HTTP requests will be read
+     *               and the HTTP responses will be written.
+     *  
+     * @throws IOException error during an I/O operation
+     */
+    public SocketState process(SocketWrapper<Socket> socketWrapper)
+        throws IOException {
+        RequestInfo rp = request.getRequestProcessor();
+        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+        // Set the remote address
+        remoteAddr = null;
+        remoteHost = null;
+        localAddr = null;
+        localName = null;
+        remotePort = -1;
+        localPort = -1;
+
+        // Setting up the I/O
+        this.socket = socketWrapper;
+        inputBuffer.setInputStream(socket.getSocket().getInputStream());
+        outputBuffer.setOutputStream(socket.getSocket().getOutputStream());
+
+        // Error flag
+        error = false;
+        keepAlive = true;
+
+        int keepAliveLeft = maxKeepAliveRequests>0?socketWrapper.decrementKeepAlive():-1;
+        
+        int soTimeout = endpoint.getSoTimeout();
+
+        try {
+            socket.getSocket().setSoTimeout(soTimeout);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.debug(sm.getString("http11processor.socket.timeout"), t);
+            error = true;
+        }
+
+        boolean keptAlive = socketWrapper.isKeptAlive();
+
+        while (!error && keepAlive && !endpoint.isPaused()) {
+
+            // Parsing the request header
+            try {
+                //TODO - calculate timeout based on length in queue (System.currentTimeMills() - wrapper.getLastAccess() is the time in queue)
+                if (keptAlive) {
+                    if (keepAliveTimeout > 0) {
+                        socket.getSocket().setSoTimeout(keepAliveTimeout);
+                    }
+                    else if (soTimeout > 0) {
+                        socket.getSocket().setSoTimeout(soTimeout);
+                    }
+                }
+                inputBuffer.parseRequestLine(false);
+                if (endpoint.isPaused()) {
+                    // 503 - Service unavailable
+                    response.setStatus(503);
+                    adapter.log(request, response, 0);
+                    error = true;
+                } else {
+                    request.setStartTime(System.currentTimeMillis());
+                    keptAlive = true;
+                    if (disableUploadTimeout) {
+                        socket.getSocket().setSoTimeout(soTimeout);
+                    } else {
+                        socket.getSocket().setSoTimeout(connectionUploadTimeout);
+                    }
+                    inputBuffer.parseHeaders();
+                }
+            } catch (IOException e) {
+                error = true;
+                break;
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("http11processor.header.parse"), t);
+                }
+                // 400 - Bad Request
+                response.setStatus(400);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+
+            if (!error) {
+                // Setting up filters, and parse some request headers
+                rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+                try {
+                    prepareRequest();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("http11processor.request.prepare"), t);
+                    }
+                    // 400 - Internal Server Error
+                    response.setStatus(400);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+
+            if (maxKeepAliveRequests > 0 && keepAliveLeft == 0)
+                keepAlive = false;
+
+            // Process the request in the adapter
+            if (!error) {
+                try {
+                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+                    adapter.service(request, response);
+                    // Handle when the response was committed before a serious
+                    // error occurred.  Throwing a ServletException should both
+                    // set the status to 500 and set the errorException.
+                    // If we fail here, then the response is likely already
+                    // committed, so we can't try and set headers.
+                    if(keepAlive && !error) { // Avoid checking twice.
+                        error = response.getErrorException() != null ||
+                                statusDropsConnection(response.getStatus());
+                    }
+
+                } catch (InterruptedIOException e) {
+                    error = true;
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("http11processor.request.process"), t);
+                    // 500 - Internal Server Error
+                    response.setStatus(500);
+                    adapter.log(request, response, 0);
+                    error = true;
+                }
+            }
+
+            // Finish the handling of the request
+            try {
+                rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
+                // If we know we are closing the connection, don't drain input.
+                // This way uploading a 100GB file doesn't tie up the thread 
+                // if the servlet has rejected it.
+                
+                if(error && !isAsync())
+                    inputBuffer.setSwallowInput(false);
+                if (!isAsync())
+                    endRequest();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error(sm.getString("http11processor.request.finish"), t);
+                // 500 - Internal Server Error
+                response.setStatus(500);
+                adapter.log(request, response, 0);
+                error = true;
+            }
+            try {
+                rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error(sm.getString("http11processor.response.finish"), t);
+                error = true;
+            }
+
+            // If there was an error, make sure the request is counted as
+            // and error, and update the statistics counter
+            if (error) {
+                response.setStatus(500);
+            }
+            request.updateCounters();
+
+            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+
+            // Don't reset the param - we'll see it as ended. Next request
+            // will reset it
+            // thrA.setParam(null);
+            // Next request
+            if (!isAsync() || error) {
+                inputBuffer.nextRequest();
+                outputBuffer.nextRequest();
+            }
+            
+            //hack keep alive behavior
+            break;
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+        if (error || endpoint.isPaused()) {
+            recycle();
+            return SocketState.CLOSED;
+        } else if (isAsync()) {
+            return SocketState.LONG;
+        } else {
+            recycle();
+            if (!keepAlive) {
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.OPEN;
+            }
+        } 
+    }
+    
+    
+    public SocketState asyncDispatch(SocketStatus status) {
+
+        RequestInfo rp = request.getRequestProcessor();
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            error = !adapter.asyncDispatch(request, response, status);
+        } catch (InterruptedIOException e) {
+            error = true;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            adapter.log(request, response, 0);
+            error = true;
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (error) {
+            return SocketState.CLOSED;
+        } else if (isAsync()) {
+            return SocketState.LONG;
+        } else {
+            if (!keepAlive) {
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.OPEN;
+            }
+        }
+    }
+
+    
+    @Override
+    protected void recycleInternal() {
+        // Recycle
+        this.socket = null;
+        // Recycle ssl info
+        sslSupport = null;
+    }
+
+
+    // ----------------------------------------------------- ActionHook Methods
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @Override
+    public void actionInternal(ActionCode actionCode, Object param) {
+
+        if (actionCode == ActionCode.CLOSE) {
+            // Close
+            // End the processing of the current request, and stop any further
+            // transactions with the client
+
+            try {
+                outputBuffer.endRequest();
+            } catch (IOException e) {
+                // Set error flag
+                error = true;
+            }
+
+        } else if (actionCode == ActionCode.REQ_SSL_ATTRIBUTE ) {
+
+            try {
+                if (sslSupport != null) {
+                    Object sslO = sslSupport.getCipherSuite();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.CIPHER_SUITE_KEY, sslO);
+                    sslO = sslSupport.getPeerCertificateChain(false);
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.CERTIFICATE_KEY, sslO);
+                    sslO = sslSupport.getKeySize();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.KEY_SIZE_KEY, sslO);
+                    sslO = sslSupport.getSessionId();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.SESSION_ID_KEY, sslO);
+                    request.setAttribute(SSLSupport.SESSION_MGR, sslSupport);
+                }
+            } catch (Exception e) {
+                log.warn(sm.getString("http11processor.socket.ssl"), e);
+            }
+
+        } else if (actionCode == ActionCode.REQ_HOST_ADDR_ATTRIBUTE) {
+
+            if ((remoteAddr == null) && (socket != null)) {
+                InetAddress inetAddr = socket.getSocket().getInetAddress();
+                if (inetAddr != null) {
+                    remoteAddr = inetAddr.getHostAddress();
+                }
+            }
+            request.remoteAddr().setString(remoteAddr);
+
+        } else if (actionCode == ActionCode.REQ_LOCAL_NAME_ATTRIBUTE) {
+
+            if ((localName == null) && (socket != null)) {
+                InetAddress inetAddr = socket.getSocket().getLocalAddress();
+                if (inetAddr != null) {
+                    localName = inetAddr.getHostName();
+                }
+            }
+            request.localName().setString(localName);
+
+        } else if (actionCode == ActionCode.REQ_HOST_ATTRIBUTE) {
+
+            if ((remoteHost == null) && (socket != null)) {
+                InetAddress inetAddr = socket.getSocket().getInetAddress();
+                if (inetAddr != null) {
+                    remoteHost = inetAddr.getHostName();
+                }
+                if(remoteHost == null) {
+                    if(remoteAddr != null) {
+                        remoteHost = remoteAddr;
+                    } else { // all we can do is punt
+                        request.remoteHost().recycle();
+                    }
+                }
+            }
+            request.remoteHost().setString(remoteHost);
+
+        } else if (actionCode == ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE) {
+
+            if (localAddr == null)
+               localAddr = socket.getSocket().getLocalAddress().getHostAddress();
+
+            request.localAddr().setString(localAddr);
+
+        } else if (actionCode == ActionCode.REQ_REMOTEPORT_ATTRIBUTE) {
+
+            if ((remotePort == -1 ) && (socket !=null)) {
+                remotePort = socket.getSocket().getPort();
+            }
+            request.setRemotePort(remotePort);
+
+        } else if (actionCode == ActionCode.REQ_LOCALPORT_ATTRIBUTE) {
+
+            if ((localPort == -1 ) && (socket !=null)) {
+                localPort = socket.getSocket().getLocalPort();
+            }
+            request.setLocalPort(localPort);
+
+        } else if (actionCode == ActionCode.REQ_SSL_CERTIFICATE) {
+            if( sslSupport != null) {
+                /*
+                 * Consume and buffer the request body, so that it does not
+                 * interfere with the client's handshake messages
+                 */
+                InputFilter[] inputFilters = inputBuffer.getFilters();
+                ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
+                    .setLimit(maxSavePostSize);
+                inputBuffer.addActiveFilter
+                    (inputFilters[Constants.BUFFERED_FILTER]);
+                try {
+                    Object sslO = sslSupport.getPeerCertificateChain(true);
+                    if( sslO != null) {
+                        request.setAttribute
+                            (SSLSupport.CERTIFICATE_KEY, sslO);
+                    }
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.ssl"), e);
+                }
+            }
+        } else if (actionCode == ActionCode.ASYNC_COMPLETE) {
+            if (asyncStateMachine.asyncComplete()) {
+                endpoint.processSocketAsync(this.socket, SocketStatus.OPEN);
+            }
+        } else if (actionCode == ActionCode.ASYNC_SETTIMEOUT) {
+            if (param == null) return;
+            long timeout = ((Long)param).longValue();
+            // if we are not piggy backing on a worker thread, set the timeout
+            socket.setTimeout(timeout);
+        } else if (actionCode == ActionCode.ASYNC_DISPATCH) {
+            if (asyncStateMachine.asyncDispatch()) {
+                endpoint.processSocketAsync(this.socket, SocketStatus.OPEN);
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------ Connector Methods
+
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * After reading the request headers, we have to setup the request filters.
+     */
+    protected void prepareRequest() {
+
+        http11 = true;
+        http09 = false;
+        contentDelimitation = false;
+        expectation = false;
+        if (sslSupport != null) {
+            request.scheme().setString("https");
+        }
+        MessageBytes protocolMB = request.protocol();
+        if (protocolMB.equals(Constants.HTTP_11)) {
+            http11 = true;
+            protocolMB.setString(Constants.HTTP_11);
+        } else if (protocolMB.equals(Constants.HTTP_10)) {
+            http11 = false;
+            keepAlive = false;
+            protocolMB.setString(Constants.HTTP_10);
+        } else if (protocolMB.equals("")) {
+            // HTTP/0.9
+            http09 = true;
+            http11 = false;
+            keepAlive = false;
+        } else {
+            // Unsupported protocol
+            http11 = false;
+            error = true;
+            // Send 505; Unsupported HTTP version
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("http11processor.request.prepare")+
+                          " Unsupported HTTP version \""+protocolMB+"\"");
+            }
+            response.setStatus(505);
+            adapter.log(request, response, 0);
+        }
+
+        MessageBytes methodMB = request.method();
+        if (methodMB.equals(Constants.GET)) {
+            methodMB.setString(Constants.GET);
+        } else if (methodMB.equals(Constants.POST)) {
+            methodMB.setString(Constants.POST);
+        }
+
+        MimeHeaders headers = request.getMimeHeaders();
+
+        // Check connection header
+        MessageBytes connectionValueMB = headers.getValue("connection");
+        if (connectionValueMB != null) {
+            ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
+            if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
+                keepAlive = false;
+            } else if (findBytes(connectionValueBC,
+                                 Constants.KEEPALIVE_BYTES) != -1) {
+                keepAlive = true;
+            }
+        }
+
+        MessageBytes expectMB = null;
+        if (http11)
+            expectMB = headers.getValue("expect");
+        if ((expectMB != null)
+            && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
+            inputBuffer.setSwallowInput(false);
+            expectation = true;
+        }
+
+        // Check user-agent header
+        if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
+            MessageBytes userAgentValueMB = headers.getValue("user-agent");
+            // Check in the restricted list, and adjust the http11
+            // and keepAlive flags accordingly
+            if(userAgentValueMB != null) {
+                String userAgentValue = userAgentValueMB.toString();
+                if (restrictedUserAgents != null &&
+                        restrictedUserAgents.matcher(userAgentValue).matches()) {
+                    http11 = false;
+                    keepAlive = false;
+                }
+            }
+        }
+
+        // Check for a full URI (including protocol://host:port/)
+        ByteChunk uriBC = request.requestURI().getByteChunk();
+        if (uriBC.startsWithIgnoreCase("http", 0)) {
+
+            int pos = uriBC.indexOf("://", 0, 3, 4);
+            int uriBCStart = uriBC.getStart();
+            int slashPos = -1;
+            if (pos != -1) {
+                byte[] uriB = uriBC.getBytes();
+                slashPos = uriBC.indexOf('/', pos + 3);
+                if (slashPos == -1) {
+                    slashPos = uriBC.getLength();
+                    // Set URI as "/"
+                    request.requestURI().setBytes
+                        (uriB, uriBCStart + pos + 1, 1);
+                } else {
+                    request.requestURI().setBytes
+                        (uriB, uriBCStart + slashPos,
+                         uriBC.getLength() - slashPos);
+                }
+                MessageBytes hostMB = headers.setValue("host");
+                hostMB.setBytes(uriB, uriBCStart + pos + 3,
+                                slashPos - pos - 3);
+            }
+
+        }
+
+        // Input filter setup
+        InputFilter[] inputFilters = inputBuffer.getFilters();
+
+        // Parse transfer-encoding header
+        MessageBytes transferEncodingValueMB = null;
+        if (http11)
+            transferEncodingValueMB = headers.getValue("transfer-encoding");
+        if (transferEncodingValueMB != null) {
+            String transferEncodingValue = transferEncodingValueMB.toString();
+            // Parse the comma separated list. "identity" codings are ignored
+            int startPos = 0;
+            int commaPos = transferEncodingValue.indexOf(',');
+            String encodingName = null;
+            while (commaPos != -1) {
+                encodingName = transferEncodingValue.substring
+                    (startPos, commaPos).toLowerCase(Locale.ENGLISH).trim();
+                if (!addInputFilter(inputFilters, encodingName)) {
+                    // Unsupported transfer encoding
+                    error = true;
+                    // 501 - Unimplemented
+                    response.setStatus(501);
+                    adapter.log(request, response, 0);
+                }
+                startPos = commaPos + 1;
+                commaPos = transferEncodingValue.indexOf(',', startPos);
+            }
+            encodingName = transferEncodingValue.substring(startPos)
+                .toLowerCase(Locale.ENGLISH).trim();
+            if (!addInputFilter(inputFilters, encodingName)) {
+                // Unsupported transfer encoding
+                error = true;
+                // 501 - Unimplemented
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("http11processor.request.prepare")+
+                              " Unsupported transfer encoding \""+encodingName+"\"");
+                }
+                response.setStatus(501);
+                adapter.log(request, response, 0);
+            }
+        }
+
+        // Parse content-length header
+        long contentLength = request.getContentLengthLong();
+        if (contentLength >= 0 && !contentDelimitation) {
+            inputBuffer.addActiveFilter
+                (inputFilters[Constants.IDENTITY_FILTER]);
+            contentDelimitation = true;
+        }
+
+        MessageBytes valueMB = headers.getValue("host");
+
+        // Check host header
+        if (http11 && (valueMB == null)) {
+            error = true;
+            // 400 - Bad request
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("http11processor.request.prepare")+
+                          " host header missing");
+            }
+            response.setStatus(400);
+            adapter.log(request, response, 0);
+        }
+
+        parseHost(valueMB);
+
+        if (!contentDelimitation) {
+            // If there's no content length 
+            // (broken HTTP/1.0 or HTTP/1.1), assume
+            // the client is not broken and didn't send a body
+            inputBuffer.addActiveFilter
+                    (inputFilters[Constants.VOID_FILTER]);
+            contentDelimitation = true;
+        }
+
+    }
+
+
+    @Override
+    protected boolean prepareSendfile(OutputFilter[] outputFilters) {
+        // Should never, ever call this code
+        Exception e = new Exception();
+        log.error(sm.getString("http11processor.neverused"), e);
+        return false;
+    }
+
+    /**
+     * Parse host.
+     */
+    protected void parseHost(MessageBytes valueMB) {
+
+        if (valueMB == null || valueMB.isNull()) {
+            // HTTP/1.0
+            // Default is what the socket tells us. Overridden if a host is
+            // found/parsed
+            request.setServerPort(socket.getSocket().getLocalPort());
+            InetAddress localAddress = socket.getSocket().getLocalAddress();
+            // Setting the socket-related fields. The adapter doesn't know
+            // about socket.
+            request.serverName().setString(localAddress.getHostName());
+            return;
+        }
+
+        ByteChunk valueBC = valueMB.getByteChunk();
+        byte[] valueB = valueBC.getBytes();
+        int valueL = valueBC.getLength();
+        int valueS = valueBC.getStart();
+        int colonPos = -1;
+        if (hostNameC.length < valueL) {
+            hostNameC = new char[valueL];
+        }
+
+        boolean ipv6 = (valueB[valueS] == '[');
+        boolean bracketClosed = false;
+        for (int i = 0; i < valueL; i++) {
+            char b = (char) valueB[i + valueS];
+            hostNameC[i] = b;
+            if (b == ']') {
+                bracketClosed = true;
+            } else if (b == ':') {
+                if (!ipv6 || bracketClosed) {
+                    colonPos = i;
+                    break;
+                }
+            }
+        }
+
+        if (colonPos < 0) {
+            if (sslSupport == null) {
+                // 80 - Default HTTP port
+                request.setServerPort(80);
+            } else {
+                // 443 - Default HTTPS port
+                request.setServerPort(443);
+            }
+            request.serverName().setChars(hostNameC, 0, valueL);
+        } else {
+
+            request.serverName().setChars(hostNameC, 0, colonPos);
+
+            int port = 0;
+            int mult = 1;
+            for (int i = valueL - 1; i > colonPos; i--) {
+                int charValue = HexUtils.getDec(valueB[i + valueS]);
+                if (charValue == -1) {
+                    // Invalid character
+                    error = true;
+                    // 400 - Bad request
+                    response.setStatus(400);
+                    adapter.log(request, response, 0);
+                    break;
+                }
+                port = port + (charValue * mult);
+                mult = 10 * mult;
+            }
+            request.setServerPort(port);
+
+        }
+
+    }
+
+    @Override
+    protected AbstractInputBuffer getInputBuffer() {
+        return inputBuffer;
+    }
+
+    @Override
+    protected AbstractOutputBuffer getOutputBuffer() {
+        return outputBuffer;
+    }
+
+    /**
+     * Set the socket buffer flag.
+     */
+    @Override
+    public void setSocketBuffer(int socketBuffer) {
+        super.setSocketBuffer(socketBuffer);
+        outputBuffer.setSocketBuffer(socketBuffer);
+    }
+
+    @Override
+    public Executor getExecutor() {
+        return endpoint.getExecutor();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11Protocol.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11Protocol.java
new file mode 100644
index 0000000..23da806
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/Http11Protocol.java
@@ -0,0 +1,305 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.net.Socket;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.ObjectName;
+
+import org.apache.coyote.RequestGroupInfo;
+import org.apache.coyote.RequestInfo;
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class Http11Protocol extends AbstractHttp11JsseProtocol {
+
+
+    private static final org.apache.juli.logging.Log log
+        = org.apache.juli.logging.LogFactory.getLog(Http11Protocol.class);
+    
+    @Override
+    protected Log getLog() { return log; }
+
+
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    public Http11Protocol() {
+        endpoint = new JIoEndpoint();
+        cHandler = new Http11ConnectionHandler(this);
+        ((JIoEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+        setProcessorCache(200);
+    }
+
+    
+    // ----------------------------------------------------------------- Fields
+
+    protected Http11ConnectionHandler cHandler;
+
+
+    // ----------------------------------------------------- JMX related methods
+
+    @Override
+    protected String getNamePrefix() {
+        return ("http-bio");
+    }
+
+
+    // -----------------------------------  Http11ConnectionHandler Inner Class
+
+    protected static class Http11ConnectionHandler implements Handler {
+
+        protected Http11Protocol proto;
+        protected AtomicLong registerCount = new AtomicLong(0);
+        protected RequestGroupInfo global = new RequestGroupInfo();
+            
+        protected ConcurrentHashMap<SocketWrapper<Socket>, Http11Processor> connections =
+            new ConcurrentHashMap<SocketWrapper<Socket>, Http11Processor>();
+
+        protected ConcurrentLinkedQueue<Http11Processor> recycledProcessors = 
+            new ConcurrentLinkedQueue<Http11Processor>() {
+            private static final long serialVersionUID = 1L;
+            protected AtomicInteger size = new AtomicInteger(0);
+            @Override
+            public boolean offer(Http11Processor processor) {
+                boolean offer = (proto.getProcessorCache() == -1) ? true : (size.get() < proto.getProcessorCache());
+                //avoid over growing our cache or add after we have stopped
+                boolean result = false;
+                if ( offer ) {
+                    result = super.offer(processor);
+                    if ( result ) {
+                        size.incrementAndGet();
+                    }
+                }
+                if (!result) unregister(processor);
+                return result;
+            }
+            
+            @Override
+            public Http11Processor poll() {
+                Http11Processor result = super.poll();
+                if ( result != null ) {
+                    size.decrementAndGet();
+                }
+                return result;
+            }
+            
+            @Override
+            public void clear() {
+                Http11Processor next = poll();
+                while ( next != null ) {
+                    unregister(next);
+                    next = poll();
+                }
+                super.clear();
+                size.set(0);
+            }
+        };
+
+        Http11ConnectionHandler(Http11Protocol proto) {
+            this.proto = proto;
+        }
+
+        @Override
+        public Object getGlobal() {
+            return global;
+        }
+
+        @Override
+        public SSLImplementation getSslImplementation() {
+            return proto.sslImplementation;
+        }
+
+        @Override
+        public void recycle() {
+            recycledProcessors.clear();
+        }
+
+        @Override
+        public SocketState process(SocketWrapper<Socket> socket) {
+            return process(socket,SocketStatus.OPEN);
+        }
+
+        @Override
+        public SocketState process(SocketWrapper<Socket> socket, SocketStatus status) {
+            Http11Processor processor = connections.remove(socket);
+            try {
+                if (processor == null) {
+                    processor = recycledProcessors.poll();
+                }
+                if (processor == null) {
+                    processor = createProcessor();
+                }
+
+                if (proto.isSSLEnabled() && (proto.sslImplementation != null)) {
+                    processor.setSSLSupport(
+                            proto.sslImplementation.getSSLSupport(
+                                    socket.getSocket()));
+                } else {
+                    processor.setSSLSupport(null);
+                }
+                
+                SocketState state = socket.isAsync()?processor.asyncDispatch(status):processor.process(socket);
+                if (state == SocketState.LONG) {
+                    connections.put(socket, processor);
+                    socket.setAsync(true);
+                    // longPoll may change socket state (e.g. to trigger a
+                    // complete or dispatch)
+                    return processor.asyncPostProcess();
+                } else {
+                    socket.setAsync(false);
+                    processor.recycle();
+                    recycledProcessors.offer(processor);
+                }
+                return state;
+            } catch(java.net.SocketException e) {
+                // SocketExceptions are normal
+                log.debug(sm.getString(
+                        "http11protocol.proto.socketexception.debug"), e);
+            } catch (java.io.IOException e) {
+                // IOExceptions are normal
+                log.debug(sm.getString(
+                        "http11protocol.proto.ioexception.debug"), e);
+            }
+            // Future developers: if you discover any other
+            // rare-but-nonfatal exceptions, catch them here, and log as
+            // above.
+            catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                // any other exception or error is odd. Here we log it
+                // with "ERROR" level, so it will show up even on
+                // less-than-verbose logs.
+                log.error(sm.getString("http11protocol.proto.error"), e);
+            }
+            processor.recycle();
+            recycledProcessors.offer(processor);
+            return SocketState.CLOSED;
+        }
+        
+        protected Http11Processor createProcessor() {
+            Http11Processor processor = new Http11Processor(
+                    proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
+                    proto.getMaxTrailerSize());
+            processor.setAdapter(proto.adapter);
+            processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
+            processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
+            processor.setConnectionUploadTimeout(
+                    proto.getConnectionUploadTimeout());
+            processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
+            processor.setCompressionMinSize(proto.getCompressionMinSize());
+            processor.setCompression(proto.getCompression());
+            processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
+            processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
+            processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
+            processor.setSocketBuffer(proto.getSocketBuffer());
+            processor.setMaxSavePostSize(proto.getMaxSavePostSize());
+            processor.setServer(proto.getServer());
+            register(processor);
+            return processor;
+        }
+        
+        protected void register(Http11Processor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        long count = registerCount.incrementAndGet();
+                        final RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(global);
+                        final ObjectName rpName = new ObjectName
+                            (proto.getDomain() + ":type=RequestProcessor,worker="
+                                + proto.getName() + ",name=HttpRequest" + count);
+                        if (log.isDebugEnabled()) {
+                            log.debug("Register " + rpName);
+                        }
+                        if (Constants.IS_SECURITY_ENABLED) {
+                            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                                @Override
+                                public Void run() {
+                                    try {
+                                        Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                                    } catch (Exception e) {
+                                        log.warn("Error registering request");
+                                    }
+                                    return null;
+                                }
+                            });
+                        } else {
+                            Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+                        }
+                        rp.setRpName(rpName);
+                    } catch (Exception e) {
+                        log.warn("Error registering request");
+                    }
+                }
+            }
+        }
+
+        protected void unregister(Http11Processor processor) {
+            if (proto.getDomain() != null) {
+                synchronized (this) {
+                    try {
+                        RequestInfo rp = processor.getRequest().getRequestProcessor();
+                        rp.setGlobalProcessor(null);
+                        ObjectName rpName = rp.getRpName();
+                        if (log.isDebugEnabled()) {
+                            log.debug("Unregister " + rpName);
+                        }
+                        Registry.getRegistry(null, null).unregisterComponent(rpName);
+                        rp.setRpName(null);
+                    } catch (Exception e) {
+                        log.warn("Error unregistering request", e);
+                    }
+                }
+            }
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InputFilter.java
new file mode 100644
index 0000000..94b7db1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InputFilter.java
@@ -0,0 +1,88 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Input filter interface.
+ * 
+ * @author Remy Maucherat
+ */
+public interface InputFilter extends InputBuffer {
+
+
+    /**
+     * Read bytes.
+     * 
+     * @return Number of bytes read.
+     */
+    @Override
+    public int doRead(ByteChunk chunk, Request unused)
+        throws IOException;
+
+
+    /**
+     * Some filters need additional parameters from the request. All the 
+     * necessary reading can occur in that method, as this method is called
+     * after the request header processing is complete.
+     */
+    public void setRequest(Request request);
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    public void recycle();
+
+
+    /**
+     * Get the name of the encoding handled by this filter.
+     */
+    public ByteChunk getEncodingName();
+
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    public void setBuffer(InputBuffer buffer);
+
+
+    /**
+     * End the current request.
+     * 
+     * @return 0 is the expected return value. A positive value indicates that
+     * too many bytes were read. This method is allowed to use buffer.doRead
+     * to consume extra bytes. The result of this method can't be negative (if
+     * an error happens, an IOException should be thrown instead).
+     */
+    public long end()
+        throws IOException;
+
+
+    /**
+     * Amount of bytes still available in a buffer.
+     */
+    public int available();
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalAprInputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalAprInputBuffer.java
new file mode 100644
index 0000000..e7be699
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalAprInputBuffer.java
@@ -0,0 +1,639 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+
+package org.apache.coyote.http11;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/**
+ * Implementation of InputBuffer which provides HTTP request header parsing as
+ * well as transfer decoding.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalAprInputBuffer extends AbstractInputBuffer {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Alternate constructor.
+     */
+    public InternalAprInputBuffer(Request request, int headerBufferSize) {
+
+        this.request = request;
+        headers = request.getMimeHeaders();
+
+        buf = new byte[headerBufferSize];
+        if (headerBufferSize < (8 * 1024)) {
+            bbuf = ByteBuffer.allocateDirect(6 * 1500);
+        } else {
+            bbuf = ByteBuffer.allocateDirect((headerBufferSize / 1500 + 1) * 1500);
+        }
+
+        inputStreamInputBuffer = new SocketInputBuffer();
+
+        filterLibrary = new InputFilter[0];
+        activeFilters = new InputFilter[0];
+        lastActiveFilter = -1;
+
+        parsingHeader = true;
+        swallowInput = true;
+        
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Direct byte buffer used to perform actual reading.
+     */
+    protected ByteBuffer bbuf;
+
+
+    /**
+     * Underlying socket.
+     */
+    protected long socket;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the underlying socket.
+     */
+    public void setSocket(long socket) {
+        this.socket = socket;
+        Socket.setrbb(this.socket, bbuf);
+    }
+
+
+    /**
+     * Get the underlying socket input stream.
+     */
+    public long getSocket() {
+        return socket;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the input buffer. This should be called when closing the 
+     * connection.
+     */
+    @Override
+    public void recycle() {
+        socket = 0;
+        super.recycle();
+    }
+
+
+    /**
+     * Read the request line. This function is meant to be used during the 
+     * HTTP request header parsing. Do NOT attempt to read the request body 
+     * using it.
+     *
+     * @throws IOException If an exception occurs during the underlying socket
+     * read operations, or if the given buffer is not big enough to accommodate
+     * the whole line.
+     * @return true if data is properly fed; false if no data is available 
+     * immediately and thread should be freed
+     */
+    @Override
+    public boolean parseRequestLine(boolean useAvailableData)
+        throws IOException {
+
+        int start = 0;
+
+        //
+        // Skipping blank lines
+        //
+
+        byte chr = 0;
+        do {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (useAvailableData) {
+                    return false;
+                }
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos++];
+
+        } while ((chr == Constants.CR) || (chr == Constants.LF));
+
+        pos--;
+
+        // Mark the current buffer position
+        start = pos;
+
+        if (pos >= lastValid) {
+            if (useAvailableData) {
+                return false;
+            }
+            if (!fill())
+                throw new EOFException(sm.getString("iib.eof.error"));
+        }
+
+        //
+        // Reading the method name
+        // Method name is always US-ASCII
+        //
+
+        boolean space = false;
+
+        while (!space) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            // Spec says no CR or LF in method name
+            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
+                throw new IllegalArgumentException(
+                        sm.getString("iib.invalidmethod"));
+            }
+            // Spec says single SP but it also says be tolerant of HT
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                space = true;
+                request.method().setBytes(buf, start, pos - start);
+            }
+
+            pos++;
+
+        }
+
+        // Spec says single SP but also says be tolerant of multiple and/or HT
+        while (space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                pos++;
+            } else {
+                space = false;
+            }
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        int end = 0;
+        int questionPos = -1;
+
+        //
+        // Reading the URI
+        //
+
+        boolean eol = false;
+
+        while (!space) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            // Spec says single SP but it also says be tolerant of HT
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == Constants.CR) 
+                       || (buf[pos] == Constants.LF)) {
+                // HTTP/0.9 style request
+                eol = true;
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == Constants.QUESTION) 
+                       && (questionPos == -1)) {
+                questionPos = pos;
+            }
+
+            pos++;
+
+        }
+
+        request.unparsedURI().setBytes(buf, start, end - start);
+        if (questionPos >= 0) {
+            request.queryString().setBytes(buf, questionPos + 1, 
+                                           end - questionPos - 1);
+            request.requestURI().setBytes(buf, start, questionPos - start);
+        } else {
+            request.requestURI().setBytes(buf, start, end - start);
+        }
+
+        // Spec says single SP but also says be tolerant of multiple and/or HT
+        while (space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                pos++;
+            } else {
+                space = false;
+            }
+        }
+
+
+        // Mark the current buffer position
+        start = pos;
+        end = 0;
+
+        //
+        // Reading the protocol
+        // Protocol is always US-ASCII
+        //
+
+        while (!eol) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            if (buf[pos] == Constants.CR) {
+                end = pos;
+            } else if (buf[pos] == Constants.LF) {
+                if (end == 0)
+                    end = pos;
+                eol = true;
+            }
+
+            pos++;
+
+        }
+
+        if ((end - start) > 0) {
+            request.protocol().setBytes(buf, start, end - start);
+        } else {
+            request.protocol().setString("");
+        }
+        
+        return true;
+
+    }
+
+
+    /**
+     * Parse the HTTP headers.
+     */
+    @Override
+    public boolean parseHeaders()
+        throws IOException {
+
+        while (parseHeader()) {
+            // Loop until there are no more headers
+        }
+
+        parsingHeader = false;
+        end = pos;
+        return true;
+    }
+
+
+    /**
+     * Parse an HTTP header.
+     * 
+     * @return false after reading a blank line (which indicates that the
+     * HTTP header parsing is done
+     */
+    @SuppressWarnings("null") // headerValue cannot be null
+    public boolean parseHeader()
+        throws IOException {
+
+        //
+        // Check for blank line
+        //
+
+        byte chr = 0;
+        while (true) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos];
+
+            if ((chr == Constants.CR) || (chr == Constants.LF)) {
+                if (chr == Constants.LF) {
+                    pos++;
+                    return false;
+                }
+            } else {
+                break;
+            }
+
+            pos++;
+
+        }
+
+        // Mark the current buffer position
+        int start = pos;
+
+        //
+        // Reading the header name
+        // Header name is always US-ASCII
+        //
+
+        boolean colon = false;
+        MessageBytes headerValue = null;
+
+        while (!colon) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            if (buf[pos] == Constants.COLON) {
+                colon = true;
+                headerValue = headers.addValue(buf, start, pos - start);
+            }
+            chr = buf[pos];
+            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
+                buf[pos] = (byte) (chr - Constants.LC_OFFSET);
+            }
+
+            pos++;
+
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        int realPos = pos;
+
+        //
+        // Reading the header value (which can be spanned over multiple lines)
+        //
+
+        boolean eol = false;
+        boolean validLine = true;
+
+        while (validLine) {
+
+            boolean space = true;
+
+            // Skipping spaces
+            while (space) {
+
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill())
+                        throw new EOFException(sm.getString("iib.eof.error"));
+                }
+
+                if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
+                    pos++;
+                } else {
+                    space = false;
+                }
+
+            }
+
+            int lastSignificantChar = realPos;
+
+            // Reading bytes until the end of the line
+            while (!eol) {
+
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill())
+                        throw new EOFException(sm.getString("iib.eof.error"));
+                }
+
+                if (buf[pos] == Constants.CR) {
+                    // Skip
+                } else if (buf[pos] == Constants.LF) {
+                    eol = true;
+                } else if (buf[pos] == Constants.SP) {
+                    buf[realPos] = buf[pos];
+                    realPos++;
+                } else {
+                    buf[realPos] = buf[pos];
+                    realPos++;
+                    lastSignificantChar = realPos;
+                }
+
+                pos++;
+
+            }
+
+            realPos = lastSignificantChar;
+
+            // Checking the first character of the new line. If the character
+            // is a LWS, then it's a multiline header
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos];
+            if ((chr != Constants.SP) && (chr != Constants.HT)) {
+                validLine = false;
+            } else {
+                eol = false;
+                // Copying one extra space in the buffer (since there must
+                // be at least one space inserted between the lines)
+                buf[realPos] = chr;
+                realPos++;
+            }
+
+        }
+
+        // Set the header value
+        headerValue.setBytes(buf, start, realPos - start);
+
+        return true;
+
+    }
+
+    
+    /**
+     * Available bytes (note that due to encoding, this may not correspond )
+     */
+    public int available() {
+        int result = (lastValid - pos);
+        if ((result == 0) && (lastActiveFilter >= 0)) {
+            for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
+                result = activeFilters[i].available();
+            }
+        }
+        return result;
+    }
+
+
+    // ---------------------------------------------------- InputBuffer Methods
+
+
+    /**
+     * Read some bytes.
+     */
+    @Override
+    public int doRead(ByteChunk chunk, Request req) 
+        throws IOException {
+
+        if (lastActiveFilter == -1)
+            return inputStreamInputBuffer.doRead(chunk, req);
+        else
+            return activeFilters[lastActiveFilter].doRead(chunk,req);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    @Override
+    protected boolean fill(boolean block) throws IOException {
+        // Ignore the block parameter and just call fill
+        return fill();
+    }
+    
+    
+    /**
+     * Fill the internal buffer using data from the underlying input stream.
+     * 
+     * @return false if at end of stream
+     */
+    protected boolean fill()
+        throws IOException {
+
+        int nRead = 0;
+
+        if (parsingHeader) {
+
+            if (lastValid == buf.length) {
+                throw new IllegalArgumentException
+                    (sm.getString("iib.requestheadertoolarge.error"));
+            }
+
+            bbuf.clear();
+            nRead = Socket.recvbb(socket, 0, buf.length - lastValid);
+            if (nRead > 0) {
+                bbuf.limit(nRead);
+                bbuf.get(buf, pos, nRead);
+                lastValid = pos + nRead;
+            } else {
+                if ((-nRead) == Status.EAGAIN) {
+                    return false;
+                } else {
+                    throw new IOException(sm.getString("iib.failedread"));
+                }
+            }
+
+        } else {
+
+            if (buf.length - end < 4500) {
+                // In this case, the request header was really large, so we allocate a 
+                // brand new one; the old one will get GCed when subsequent requests
+                // clear all references
+                buf = new byte[buf.length];
+                end = 0;
+            }
+            pos = end;
+            lastValid = pos;
+            bbuf.clear();
+            nRead = Socket.recvbb(socket, 0, buf.length - lastValid);
+            if (nRead > 0) {
+                bbuf.limit(nRead);
+                bbuf.get(buf, pos, nRead);
+                lastValid = pos + nRead;
+            } else {
+                if ((-nRead) == Status.ETIMEDOUT || (-nRead) == Status.TIMEUP) {
+                    throw new SocketTimeoutException(sm.getString("iib.failedread"));
+                } else {
+                    throw new IOException(sm.getString("iib.failedread"));
+                }
+            }
+
+        }
+
+        return (nRead > 0);
+
+    }
+
+
+    // ------------------------------------- InputStreamInputBuffer Inner Class
+
+
+    /**
+     * This class is an input buffer which will read its data from an input
+     * stream.
+     */
+    protected class SocketInputBuffer 
+        implements InputBuffer {
+
+
+        /**
+         * Read bytes into the specified chunk.
+         */
+        @Override
+        public int doRead(ByteChunk chunk, Request req ) 
+            throws IOException {
+
+            if (pos >= lastValid) {
+                if (!fill())
+                    return -1;
+            }
+
+            int length = lastValid - pos;
+            chunk.setBytes(buf, pos, length);
+            pos = lastValid;
+
+            return (length);
+
+        }
+
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalAprOutputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalAprOutputBuffer.java
new file mode 100644
index 0000000..53b331b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalAprOutputBuffer.java
@@ -0,0 +1,258 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.http.HttpMessages;
+
+/**
+ * Output buffer.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalAprOutputBuffer extends AbstractOutputBuffer {
+
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Default constructor.
+     */
+    public InternalAprOutputBuffer(Response response, int headerBufferSize) {
+
+        this.response = response;
+
+        buf = new byte[headerBufferSize];
+        if (headerBufferSize < (8 * 1024)) {
+            bbuf = ByteBuffer.allocateDirect(6 * 1500);
+        } else {
+            bbuf = ByteBuffer.allocateDirect((headerBufferSize / 1500 + 1) * 1500);
+        }
+
+        outputStreamOutputBuffer = new SocketOutputBuffer();
+
+        filterLibrary = new OutputFilter[0];
+        activeFilters = new OutputFilter[0];
+        lastActiveFilter = -1;
+
+        committed = false;
+        finished = false;
+
+        // Cause loading of HttpMessages
+        HttpMessages.getMessage(200);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Underlying socket.
+     */
+    protected long socket;
+
+
+    /**
+     * Direct byte buffer used for writing.
+     */
+    protected ByteBuffer bbuf = null;
+
+    
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the underlying socket.
+     */
+    public void setSocket(long socket) {
+        this.socket = socket;
+        Socket.setsbb(this.socket, bbuf);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Flush the response.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    public void flush()
+        throws IOException {
+
+        super.flush();
+
+        // Flush the current buffer
+        flushBuffer();
+    }
+
+
+    /**
+     * Recycle the output buffer. This should be called when closing the 
+     * connection.
+     */
+    @Override
+    public void recycle() {
+
+        super.recycle();
+        
+        bbuf.clear();
+    }
+
+
+    /**
+     * End request.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    public void endRequest()
+        throws IOException {
+
+        if (!committed) {
+
+            // Send the connector a request for commit. The connector should
+            // then validate the headers, send them (using sendHeader) and 
+            // set the filters accordingly.
+            response.action(ActionCode.COMMIT, null);
+
+        }
+
+        if (finished)
+            return;
+
+        if (lastActiveFilter != -1)
+            activeFilters[lastActiveFilter].end();
+
+        flushBuffer();
+
+        finished = true;
+
+    }
+
+
+    // ------------------------------------------------ HTTP/1.1 Output Methods
+
+
+    /**
+     * Send an acknowledgment.
+     */
+    @Override
+    public void sendAck()
+        throws IOException {
+
+        if (!committed) {
+            if (Socket.send(socket, Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length) < 0)
+                throw new IOException(sm.getString("iib.failedwrite"));
+        }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Commit the response.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    protected void commit() throws IOException {
+
+        // The response is now committed
+        committed = true;
+        response.setCommitted(true);
+
+        if (pos > 0) {
+            // Sending the response header buffer
+            bbuf.put(buf, 0, pos);
+        }
+
+    }
+
+
+
+    /**
+     * Callback to write data from the buffer.
+     */
+    protected void flushBuffer()
+        throws IOException {
+        if (bbuf.position() > 0) {
+            if (Socket.sendbb(socket, 0, bbuf.position()) < 0) {
+                throw new IOException();
+            }
+            bbuf.clear();
+        }
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer implements OutputBuffer {
+
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res) 
+            throws IOException {
+
+            int len = chunk.getLength();
+            int start = chunk.getStart();
+            byte[] b = chunk.getBuffer();
+            while (len > 0) {
+                int thisTime = len;
+                if (bbuf.position() == bbuf.capacity()) {
+                    flushBuffer();
+                }
+                if (thisTime > bbuf.capacity() - bbuf.position()) {
+                    thisTime = bbuf.capacity() - bbuf.position();
+                }
+                bbuf.put(b, start, thisTime);
+                len = len - thisTime;
+                start = start + thisTime;
+            }
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalInputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalInputBuffer.java
new file mode 100644
index 0000000..101a96c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalInputBuffer.java
@@ -0,0 +1,508 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+
+package org.apache.coyote.http11;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/**
+ * Implementation of InputBuffer which provides HTTP request header parsing as
+ * well as transfer decoding.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalInputBuffer extends AbstractInputBuffer {
+
+    /**
+     * Default constructor.
+     */
+    public InternalInputBuffer(Request request, int headerBufferSize) {
+
+        this.request = request;
+        headers = request.getMimeHeaders();
+
+        buf = new byte[headerBufferSize];
+
+        inputStreamInputBuffer = new InputStreamInputBuffer();
+
+        filterLibrary = new InputFilter[0];
+        activeFilters = new InputFilter[0];
+        lastActiveFilter = -1;
+
+        parsingHeader = true;
+        swallowInput = true;
+
+    }
+
+    /**
+     * Read the request line. This function is meant to be used during the 
+     * HTTP request header parsing. Do NOT attempt to read the request body 
+     * using it.
+     *
+     * @throws IOException If an exception occurs during the underlying socket
+     * read operations, or if the given buffer is not big enough to accommodate
+     * the whole line.
+     */
+    @Override
+    public boolean parseRequestLine(boolean useAvailableDataOnly)
+    
+        throws IOException {
+
+        int start = 0;
+
+        //
+        // Skipping blank lines
+        //
+
+        byte chr = 0;
+        do {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos++];
+
+        } while ((chr == Constants.CR) || (chr == Constants.LF));
+
+        pos--;
+
+        // Mark the current buffer position
+        start = pos;
+
+        //
+        // Reading the method name
+        // Method name is always US-ASCII
+        //
+
+        boolean space = false;
+
+        while (!space) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            // Spec says no CR or LF in method name
+            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
+                throw new IllegalArgumentException(
+                        sm.getString("iib.invalidmethod"));
+            }
+            // Spec says single SP but it also says be tolerant of HT
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                space = true;
+                request.method().setBytes(buf, start, pos - start);
+            }
+
+            pos++;
+
+        }
+
+        
+        // Spec says single SP but also says be tolerant of multiple and/or HT
+        while (space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                pos++;
+            } else {
+                space = false;
+            }
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        int end = 0;
+        int questionPos = -1;
+
+        //
+        // Reading the URI
+        //
+
+        boolean eol = false;
+
+        while (!space) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            // Spec says single SP but it also says be tolerant of HT
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == Constants.CR) 
+                       || (buf[pos] == Constants.LF)) {
+                // HTTP/0.9 style request
+                eol = true;
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == Constants.QUESTION) 
+                       && (questionPos == -1)) {
+                questionPos = pos;
+            }
+
+            pos++;
+
+        }
+
+        request.unparsedURI().setBytes(buf, start, end - start);
+        if (questionPos >= 0) {
+            request.queryString().setBytes(buf, questionPos + 1, 
+                                           end - questionPos - 1);
+            request.requestURI().setBytes(buf, start, questionPos - start);
+        } else {
+            request.requestURI().setBytes(buf, start, end - start);
+        }
+
+        // Spec says single SP but also says be tolerant of multiple and/or HT
+        while (space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                pos++;
+            } else {
+                space = false;
+            }
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        end = 0;
+
+        //
+        // Reading the protocol
+        // Protocol is always US-ASCII
+        //
+
+        while (!eol) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            if (buf[pos] == Constants.CR) {
+                end = pos;
+            } else if (buf[pos] == Constants.LF) {
+                if (end == 0)
+                    end = pos;
+                eol = true;
+            }
+
+            pos++;
+
+        }
+
+        if ((end - start) > 0) {
+            request.protocol().setBytes(buf, start, end - start);
+        } else {
+            request.protocol().setString("");
+        }
+        
+        return true;
+
+    }
+
+
+    /**
+     * Parse the HTTP headers.
+     */
+    @Override
+    public boolean parseHeaders()
+        throws IOException {
+
+        while (parseHeader()) {
+            // Loop until we run out of headers
+        }
+
+        parsingHeader = false;
+        end = pos;
+        return true;
+    }
+
+
+    /**
+     * Parse an HTTP header.
+     * 
+     * @return false after reading a blank line (which indicates that the
+     * HTTP header parsing is done
+     */
+    @SuppressWarnings("null") // headerValue cannot be null
+    public boolean parseHeader()
+        throws IOException {
+
+        //
+        // Check for blank line
+        //
+
+        byte chr = 0;
+        while (true) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos];
+
+            if ((chr == Constants.CR) || (chr == Constants.LF)) {
+                if (chr == Constants.LF) {
+                    pos++;
+                    return false;
+                }
+            } else {
+                break;
+            }
+
+            pos++;
+
+        }
+
+        // Mark the current buffer position
+        int start = pos;
+
+        //
+        // Reading the header name
+        // Header name is always US-ASCII
+        //
+
+        boolean colon = false;
+        MessageBytes headerValue = null;
+
+        while (!colon) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            if (buf[pos] == Constants.COLON) {
+                colon = true;
+                headerValue = headers.addValue(buf, start, pos - start);
+            }
+            chr = buf[pos];
+            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
+                buf[pos] = (byte) (chr - Constants.LC_OFFSET);
+            }
+
+            pos++;
+
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        int realPos = pos;
+
+        //
+        // Reading the header value (which can be spanned over multiple lines)
+        //
+
+        boolean eol = false;
+        boolean validLine = true;
+
+        while (validLine) {
+
+            boolean space = true;
+
+            // Skipping spaces
+            while (space) {
+
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill())
+                        throw new EOFException(sm.getString("iib.eof.error"));
+                }
+
+                if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
+                    pos++;
+                } else {
+                    space = false;
+                }
+
+            }
+
+            int lastSignificantChar = realPos;
+
+            // Reading bytes until the end of the line
+            while (!eol) {
+
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill())
+                        throw new EOFException(sm.getString("iib.eof.error"));
+                }
+
+                if (buf[pos] == Constants.CR) {
+                    // Skip
+                } else if (buf[pos] == Constants.LF) {
+                    eol = true;
+                } else if (buf[pos] == Constants.SP) {
+                    buf[realPos] = buf[pos];
+                    realPos++;
+                } else {
+                    buf[realPos] = buf[pos];
+                    realPos++;
+                    lastSignificantChar = realPos;
+                }
+
+                pos++;
+
+            }
+
+            realPos = lastSignificantChar;
+
+            // Checking the first character of the new line. If the character
+            // is a LWS, then it's a multiline header
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos];
+            if ((chr != Constants.SP) && (chr != Constants.HT)) {
+                validLine = false;
+            } else {
+                eol = false;
+                // Copying one extra space in the buffer (since there must
+                // be at least one space inserted between the lines)
+                buf[realPos] = chr;
+                realPos++;
+            }
+
+        }
+
+        // Set the header value
+        headerValue.setBytes(buf, start, realPos - start);
+
+        return true;
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Fill the internal buffer using data from the underlying input stream.
+     * 
+     * @return false if at end of stream
+     */
+    protected boolean fill() throws IOException {
+        return fill(true);
+    }
+
+    @Override
+    protected boolean fill(boolean block) throws IOException {
+
+        int nRead = 0;
+
+        if (parsingHeader) {
+
+            if (lastValid == buf.length) {
+                throw new IllegalArgumentException
+                    (sm.getString("iib.requestheadertoolarge.error"));
+            }
+
+            nRead = inputStream.read(buf, pos, buf.length - lastValid);
+            if (nRead > 0) {
+                lastValid = pos + nRead;
+            }
+
+        } else {
+
+            if (buf.length - end < 4500) {
+                // In this case, the request header was really large, so we allocate a 
+                // brand new one; the old one will get GCed when subsequent requests
+                // clear all references
+                buf = new byte[buf.length];
+                end = 0;
+            }
+            pos = end;
+            lastValid = pos;
+            nRead = inputStream.read(buf, pos, buf.length - lastValid);
+            if (nRead > 0) {
+                lastValid = pos + nRead;
+            }
+
+        }
+
+        return (nRead > 0);
+
+    }
+
+
+    // ------------------------------------- InputStreamInputBuffer Inner Class
+
+
+    /**
+     * This class is an input buffer which will read its data from an input
+     * stream.
+     */
+    protected class InputStreamInputBuffer 
+        implements InputBuffer {
+
+
+        /**
+         * Read bytes into the specified chunk.
+         */
+        @Override
+        public int doRead(ByteChunk chunk, Request req ) 
+            throws IOException {
+
+            if (pos >= lastValid) {
+                if (!fill())
+                    return -1;
+            }
+
+            int length = lastValid - pos;
+            chunk.setBytes(buf, pos, length);
+            pos = lastValid;
+
+            return (length);
+
+        }
+
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalNioInputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalNioInputBuffer.java
new file mode 100644
index 0000000..dfa3f67
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalNioInputBuffer.java
@@ -0,0 +1,776 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+
+package org.apache.coyote.http11;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.channels.Selector;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioSelectorPool;
+
+/**
+ * Implementation of InputBuffer which provides HTTP request header parsing as
+ * well as transfer decoding.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @author Filip Hanik
+ */
+public class InternalNioInputBuffer extends AbstractInputBuffer {
+
+    /**
+     * Logger.
+     */
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(InternalNioInputBuffer.class);
+
+    // -------------------------------------------------------------- Constants
+
+    enum HeaderParseStatus {DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA}
+    enum HeaderParsePosition {HEADER_START, HEADER_NAME, HEADER_VALUE, HEADER_MULTI_LINE}
+    // ----------------------------------------------------------- Constructors
+    
+
+    /**
+     * Alternate constructor.
+     */
+    public InternalNioInputBuffer(Request request, int headerBufferSize) {
+
+        this.request = request;
+        headers = request.getMimeHeaders();
+
+        this.headerBufferSize = headerBufferSize;
+
+        inputStreamInputBuffer = new SocketInputBuffer();
+
+        filterLibrary = new InputFilter[0];
+        activeFilters = new InputFilter[0];
+        lastActiveFilter = -1;
+
+        parsingHeader = true;
+        parsingRequestLine = true;
+        parsingRequestLinePhase = 0;
+        parsingRequestLineEol = false;
+        parsingRequestLineStart = 0;
+        parsingRequestLineQPos = -1;
+        headerParsePos = HeaderParsePosition.HEADER_START;
+        headerData.recycle();
+        swallowInput = true;
+
+    }
+
+    /**
+     * Parsing state - used for non blocking parsing so that
+     * when more data arrives, we can pick up where we left off.
+     */
+    protected boolean parsingRequestLine;
+    protected int parsingRequestLinePhase = 0;
+    protected boolean parsingRequestLineEol = false;
+    protected int parsingRequestLineStart = 0;
+    protected int parsingRequestLineQPos = -1;
+    protected HeaderParsePosition headerParsePos;
+
+    /**
+     * Underlying socket.
+     */
+    protected NioChannel socket;
+    
+    /**
+     * Selector pool, for blocking reads and blocking writes
+     */
+    protected NioSelectorPool pool;
+
+
+    /**
+     * Maximum allowed size of the HTTP request line plus headers.
+     */
+    private final int headerBufferSize;
+
+    /**
+     * Known size of the NioChannel read buffer.
+     */
+    private int socketReadBufferSize;
+
+    /**
+     * Additional size we allocate to the buffer to be more effective when
+     * skipping empty lines that may precede the request.
+     */
+    private static final int skipBlankLinesSize = 1024;
+
+    /**
+     * How many bytes in the buffer are occupied by skipped blank lines that
+     * precede the request.
+     */
+    private int skipBlankLinesBytes;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the underlying socket.
+     */
+    public void setSocket(NioChannel socket) {
+        this.socket = socket;
+        socketReadBufferSize = socket.getBufHandler().getReadBuffer().capacity();
+        int bufLength = skipBlankLinesSize + headerBufferSize
+                + socketReadBufferSize;
+        if (buf == null || buf.length < bufLength) {
+            buf = new byte[bufLength];
+        }
+    }
+    
+    /**
+     * Get the underlying socket input stream.
+     */
+    public NioChannel getSocket() {
+        return socket;
+    }
+
+    public void setSelectorPool(NioSelectorPool pool) { 
+        this.pool = pool;
+    }
+    
+    public NioSelectorPool getSelectorPool() {
+        return pool;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+    /**
+     * Issues a non blocking read
+     * @return int
+     * @throws IOException
+     */
+    public int nbRead() throws IOException {
+        return readSocket(true,false);
+    }
+
+    /**
+     * Recycle the input buffer. This should be called when closing the 
+     * connection.
+     */
+    @Override
+    public void recycle() {
+        // Recycle filters
+        for (int i = 0; i <= lastActiveFilter; i++) {
+            activeFilters[i].recycle();
+        }
+        // This must be after filters since it resets the lastFilterIndex
+        super.recycle();
+        socket = null;
+        headerParsePos = HeaderParsePosition.HEADER_START;
+        parsingRequestLine = true;
+        parsingRequestLinePhase = 0;
+        parsingRequestLineEol = false;
+        parsingRequestLineStart = 0;
+        parsingRequestLineQPos = -1;
+        headerData.recycle();
+    }
+
+
+    /**
+     * End processing of current HTTP request.
+     * Note: All bytes of the current request should have been already 
+     * consumed. This method only resets all the pointers so that we are ready
+     * to parse the next HTTP request.
+     */
+    @Override
+    public void nextRequest() {
+        super.nextRequest();
+        headerParsePos = HeaderParsePosition.HEADER_START;
+        parsingRequestLine = true;
+        parsingRequestLinePhase = 0;
+        parsingRequestLineEol = false;
+        parsingRequestLineStart = 0;
+        parsingRequestLineQPos = -1;
+        headerData.recycle();
+    }
+
+    /**
+     * Read the request line. This function is meant to be used during the 
+     * HTTP request header parsing. Do NOT attempt to read the request body 
+     * using it.
+     *
+     * @throws IOException If an exception occurs during the underlying socket
+     * read operations, or if the given buffer is not big enough to accommodate
+     * the whole line.
+     * @return true if data is properly fed; false if no data is available 
+     * immediately and thread should be freed
+     */
+    @Override
+    public boolean parseRequestLine(boolean useAvailableDataOnly)
+        throws IOException {
+
+        //check state
+        if ( !parsingRequestLine ) return true;
+        //
+        // Skipping blank lines
+        //
+        if ( parsingRequestLinePhase == 0 ) {
+            byte chr = 0;
+            do {
+                
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (useAvailableDataOnly) {
+                        return false;
+                    }
+                    // Ignore bytes that were read
+                    pos = lastValid = 0;
+                    // Do a simple read with a short timeout
+                    if ( readSocket(true, false)==0 ) return false;
+                }
+                chr = buf[pos++];
+            } while ((chr == Constants.CR) || (chr == Constants.LF));
+            pos--;
+            if (pos >= skipBlankLinesSize) {
+                // Move data, to have enough space for further reading
+                // of headers and body
+                System.arraycopy(buf, pos, buf, 0, lastValid - pos);
+                lastValid -= pos;
+                pos = 0;
+            }
+            skipBlankLinesBytes = pos;
+            parsingRequestLineStart = pos;
+            parsingRequestLinePhase = 2;
+            if (log.isDebugEnabled()) {
+                log.debug("Received ["
+                        + new String(buf, pos, lastValid - pos, "ISO-8859-1")
+                        + "]");
+            }
+        }
+        if ( parsingRequestLinePhase == 2 ) {
+            //
+            // Reading the method name
+            // Method name is always US-ASCII
+            //
+            boolean space = false;
+            while (!space) {
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill(true, false)) //request line parsing
+                        return false;
+                }
+                // Spec says no CR or LF in method name
+                if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
+                    throw new IllegalArgumentException(
+                            sm.getString("iib.invalidmethod"));
+                }
+                if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                    space = true;
+                    request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart);
+                }
+                pos++;
+            }
+            parsingRequestLinePhase = 3;
+        }
+        if ( parsingRequestLinePhase == 3 ) {
+            // Spec says single SP but also be tolerant of multiple and/or HT
+            boolean space = true;
+            while (space) {
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill(true, false)) //request line parsing
+                        return false;
+                }
+                if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                    pos++;
+                } else {
+                    space = false;
+                }
+            }
+            parsingRequestLineStart = pos;
+            parsingRequestLinePhase = 4;
+        }
+        if (parsingRequestLinePhase == 4) {
+            // Mark the current buffer position
+            
+            int end = 0;
+            //
+            // Reading the URI
+            //
+            boolean space = false;
+            while (!space) {
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill(true,false)) //request line parsing
+                        return false;
+                }
+                if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                    space = true;
+                    end = pos;
+                } else if ((buf[pos] == Constants.CR) 
+                           || (buf[pos] == Constants.LF)) {
+                    // HTTP/0.9 style request
+                    parsingRequestLineEol = true;
+                    space = true;
+                    end = pos;
+                } else if ((buf[pos] == Constants.QUESTION) 
+                           && (parsingRequestLineQPos == -1)) {
+                    parsingRequestLineQPos = pos;
+                }
+                pos++;
+            }
+            request.unparsedURI().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart);
+            if (parsingRequestLineQPos >= 0) {
+                request.queryString().setBytes(buf, parsingRequestLineQPos + 1, 
+                                               end - parsingRequestLineQPos - 1);
+                request.requestURI().setBytes(buf, parsingRequestLineStart, parsingRequestLineQPos - parsingRequestLineStart);
+            } else {
+                request.requestURI().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart);
+            }
+            parsingRequestLinePhase = 5;
+        }
+        if ( parsingRequestLinePhase == 5 ) {
+            // Spec says single SP but also be tolerant of multiple and/or HT
+            boolean space = true;
+            while (space) {
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill(true, false)) //request line parsing
+                        return false;
+                }
+                if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                    pos++;
+                } else {
+                    space = false;
+                }
+            }
+            parsingRequestLineStart = pos;
+            parsingRequestLinePhase = 6;
+        }
+        if (parsingRequestLinePhase == 6) { 
+            // Mark the current buffer position
+            
+            end = 0;
+            //
+            // Reading the protocol
+            // Protocol is always US-ASCII
+            //
+            while (!parsingRequestLineEol) {
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill(true, false)) //request line parsing
+                        return false;
+                }
+        
+                if (buf[pos] == Constants.CR) {
+                    end = pos;
+                } else if (buf[pos] == Constants.LF) {
+                    if (end == 0)
+                        end = pos;
+                    parsingRequestLineEol = true;
+                }
+                pos++;
+            }
+        
+            if ( (end - parsingRequestLineStart) > 0) {
+                request.protocol().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart);
+            } else {
+                request.protocol().setString("");
+            }
+            parsingRequestLine = false;
+            parsingRequestLinePhase = 0;
+            parsingRequestLineEol = false;
+            parsingRequestLineStart = 0;
+            return true;
+        }
+        throw new IllegalStateException("Invalid request line parse phase:"+parsingRequestLinePhase);
+    }
+    
+    private void expand(int newsize) {
+        if ( newsize > buf.length ) {
+            if (parsingHeader) {
+                throw new IllegalArgumentException(
+                        sm.getString("iib.requestheadertoolarge.error"));
+            }
+            // Should not happen
+            log.warn("Expanding buffer size. Old size: " + buf.length
+                    + ", new size: " + newsize, new Exception());
+            byte[] tmp = new byte[newsize];
+            System.arraycopy(buf,0,tmp,0,buf.length);
+            buf = tmp;
+        }
+    }
+    
+    /**
+     * Perform blocking read with a timeout if desired
+     * @param timeout boolean - if we want to use the timeout data
+     * @param block - true if the system should perform a blocking read, false otherwise
+     * @return boolean - true if data was read, false is no data read, EOFException if EOF is reached
+     * @throws IOException if a socket exception occurs
+     * @throws EOFException if end of stream is reached
+     */
+    
+    private int readSocket(boolean timeout, boolean block) throws IOException {
+        int nRead = 0;
+        socket.getBufHandler().getReadBuffer().clear();
+        if ( block ) {
+            Selector selector = null;
+            try {
+                selector = getSelectorPool().get();
+            } catch ( IOException x ) {
+                // Ignore
+            }
+            try {
+                NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+                if ( att == null ) throw new IOException("Key must be cancelled.");
+                nRead = getSelectorPool().read(socket.getBufHandler().getReadBuffer(),socket,selector,att.getTimeout());
+            } catch ( EOFException eof ) {
+                nRead = -1;
+            } finally { 
+                if ( selector != null ) getSelectorPool().put(selector);
+            }
+        } else {
+            nRead = socket.read(socket.getBufHandler().getReadBuffer());
+        }
+        if (nRead > 0) {
+            socket.getBufHandler().getReadBuffer().flip();
+            socket.getBufHandler().getReadBuffer().limit(nRead);
+            expand(nRead + pos);
+            socket.getBufHandler().getReadBuffer().get(buf, pos, nRead);
+            lastValid = pos + nRead;
+            return nRead;
+        } else if (nRead == -1) {
+            //return false;
+            throw new EOFException(sm.getString("iib.eof.error"));
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Parse the HTTP headers.
+     */
+    @Override
+    public boolean parseHeaders()
+        throws IOException {
+        HeaderParseStatus status = HeaderParseStatus.HAVE_MORE_HEADERS;
+        
+        do {
+            status = parseHeader();
+        } while ( status == HeaderParseStatus.HAVE_MORE_HEADERS );
+        if (status == HeaderParseStatus.DONE) {
+            parsingHeader = false;
+            end = pos;
+            // Checking that
+            // (1) Headers plus request line size does not exceed its limit
+            // (2) There are enough bytes to avoid expanding the buffer when
+            // reading body
+            // Technically, (2) is technical limitation, (1) is logical
+            // limitation to enforce the meaning of headerBufferSize
+            // From the way how buf is allocated and how blank lines are being
+            // read, it should be enough to check (1) only.
+            if (end - skipBlankLinesBytes > headerBufferSize
+                    || buf.length - end < socketReadBufferSize) {
+                throw new IllegalArgumentException(
+                        sm.getString("iib.requestheadertoolarge.error"));
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    /**
+     * Parse an HTTP header.
+     * 
+     * @return false after reading a blank line (which indicates that the
+     * HTTP header parsing is done
+     */
+    public HeaderParseStatus parseHeader()
+        throws IOException {
+
+        //
+        // Check for blank line
+        //
+
+        byte chr = 0;
+        while (headerParsePos == HeaderParsePosition.HEADER_START) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill(true,false)) {//parse header 
+                    headerParsePos = HeaderParsePosition.HEADER_START;
+                    return HeaderParseStatus.NEED_MORE_DATA;
+                }
+            }
+
+            chr = buf[pos];
+
+            if ((chr == Constants.CR) || (chr == Constants.LF)) {
+                if (chr == Constants.LF) {
+                    pos++;
+                    return HeaderParseStatus.DONE;
+                }
+            } else {
+                break;
+            }
+
+            pos++;
+
+        }
+
+        if ( headerParsePos == HeaderParsePosition.HEADER_START ) {
+            // Mark the current buffer position
+            headerData.start = pos;
+            headerParsePos = HeaderParsePosition.HEADER_NAME;
+        }    
+
+        //
+        // Reading the header name
+        // Header name is always US-ASCII
+        //
+
+
+        while (headerParsePos == HeaderParsePosition.HEADER_NAME) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill(true,false)) { //parse header 
+                    return HeaderParseStatus.NEED_MORE_DATA;
+                }
+            }
+
+            if (buf[pos] == Constants.COLON) {
+                headerParsePos = HeaderParsePosition.HEADER_VALUE;
+                headerData.headerValue = headers.addValue(buf, headerData.start, pos - headerData.start);
+            }
+            chr = buf[pos];
+            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
+                buf[pos] = (byte) (chr - Constants.LC_OFFSET);
+            }
+
+            pos++;
+            if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) { 
+                // Mark the current buffer position
+                headerData.start = pos;
+                headerData.realPos = pos;
+            }
+        }
+
+        
+        //
+        // Reading the header value (which can be spanned over multiple lines)
+        //
+
+        boolean eol = false;
+
+        while (headerParsePos == HeaderParsePosition.HEADER_VALUE ||
+               headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
+            if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) {
+            
+                boolean space = true;
+
+                // Skipping spaces
+                while (space) {
+
+                    // Read new bytes if needed
+                    if (pos >= lastValid) {
+                        if (!fill(true,false)) {//parse header 
+                            //HEADER_VALUE, should already be set
+                            return HeaderParseStatus.NEED_MORE_DATA;
+                        }
+                    }
+
+                    if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
+                        pos++;
+                    } else {
+                        space = false;
+                    }
+
+                }
+
+                headerData.lastSignificantChar = headerData.realPos;
+
+                // Reading bytes until the end of the line
+                while (!eol) {
+
+                    // Read new bytes if needed
+                    if (pos >= lastValid) {
+                        if (!fill(true,false)) {//parse header 
+                            //HEADER_VALUE
+                            return HeaderParseStatus.NEED_MORE_DATA;
+                        }
+
+                    }
+
+                    if (buf[pos] == Constants.CR) {
+                        // Skip
+                    } else if (buf[pos] == Constants.LF) {
+                        eol = true;
+                    } else if (buf[pos] == Constants.SP) {
+                        buf[headerData.realPos] = buf[pos];
+                        headerData.realPos++;
+                    } else {
+                        buf[headerData.realPos] = buf[pos];
+                        headerData.realPos++;
+                        headerData.lastSignificantChar = headerData.realPos;
+                    }
+
+                    pos++;
+
+                }
+
+                headerData.realPos = headerData.lastSignificantChar;
+
+                // Checking the first character of the new line. If the character
+                // is a LWS, then it's a multiline header
+                headerParsePos = HeaderParsePosition.HEADER_MULTI_LINE;
+            }
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill(true,false)) {//parse header
+                    
+                    //HEADER_MULTI_LINE
+                    return HeaderParseStatus.NEED_MORE_DATA;
+                }
+            }
+
+            chr = buf[pos];
+            if ( headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE ) {
+                if ( (chr != Constants.SP) && (chr != Constants.HT)) {
+                    headerParsePos = HeaderParsePosition.HEADER_START;
+                } else {
+                    eol = false;
+                    // Copying one extra space in the buffer (since there must
+                    // be at least one space inserted between the lines)
+                    buf[headerData.realPos] = chr;
+                    headerData.realPos++;
+                    headerParsePos = HeaderParsePosition.HEADER_VALUE;
+                }
+            }
+        }
+        // Set the header value
+        headerData.headerValue.setBytes(buf, headerData.start, headerData.realPos - headerData.start);
+        headerData.recycle();
+        return HeaderParseStatus.HAVE_MORE_HEADERS;
+    }
+    
+    protected HeaderParseData headerData = new HeaderParseData();
+    public static class HeaderParseData {
+        int start = 0;
+        int realPos = 0;
+        int lastSignificantChar = 0;
+        MessageBytes headerValue = null;
+        public void recycle() {
+            start = 0;
+            realPos = 0;
+            lastSignificantChar = 0;
+            headerValue = null;
+        }
+    }
+
+
+    /**
+     * Available bytes (note that due to encoding, this may not correspond )
+     */
+    public int available() {
+        int result = (lastValid - pos);
+        if ((result == 0) && (lastActiveFilter >= 0)) {
+            for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
+                result = activeFilters[i].available();
+            }
+        }
+        return result;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+    /**
+     * Fill the internal buffer using data from the underlying input stream.
+     * 
+     * @return false if at end of stream
+     */
+    @Override
+    protected boolean fill(boolean block) throws IOException, EOFException {
+        return fill(true,block);
+    }
+
+    protected boolean fill(boolean timeout, boolean block) throws IOException, EOFException {
+        
+
+        boolean read = false;
+
+        if (parsingHeader) {
+
+            if (lastValid == buf.length) {
+                throw new IllegalArgumentException
+                    (sm.getString("iib.requestheadertoolarge.error"));
+            }
+
+            // Do a simple read with a short timeout
+            read = readSocket(timeout,block)>0;
+        } else {
+            lastValid = pos = end;
+            // Do a simple read with a short timeout
+            read = readSocket(timeout, block)>0;
+        }
+        return read;
+    }
+
+
+    // ------------------------------------- InputStreamInputBuffer Inner Class
+
+
+    /**
+     * This class is an input buffer which will read its data from an input
+     * stream.
+     */
+    protected class SocketInputBuffer 
+        implements InputBuffer {
+
+
+        /**
+         * Read bytes into the specified chunk.
+         */
+        @Override
+        public int doRead(ByteChunk chunk, Request req ) 
+            throws IOException {
+
+            if (pos >= lastValid) {
+                if (!fill(true,true)) //read body, must be blocking, as the thread is inside the app
+                    return -1;
+            }
+
+            int length = lastValid - pos;
+            chunk.setBytes(buf, pos, length);
+            pos = lastValid;
+
+            return (length);
+
+        }
+
+
+    }
+
+
+    public int getParsingRequestLinePhase() {
+        return parsingRequestLinePhase;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalNioOutputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalNioOutputBuffer.java
new file mode 100644
index 0000000..ecdd7e7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalNioOutputBuffer.java
@@ -0,0 +1,299 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.MutableInteger;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.http.HttpMessages;
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioSelectorPool;
+
+/**
+ * Output buffer.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @author Filip Hanik
+ */
+public class InternalNioOutputBuffer extends AbstractOutputBuffer {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Default constructor.
+     */
+    public InternalNioOutputBuffer(Response response, int headerBufferSize) {
+
+        this.response = response;
+
+        buf = new byte[headerBufferSize];
+        
+        outputStreamOutputBuffer = new SocketOutputBuffer();
+
+        filterLibrary = new OutputFilter[0];
+        activeFilters = new OutputFilter[0];
+        lastActiveFilter = -1;
+
+        committed = false;
+        finished = false;
+        
+        // Cause loading of HttpMessages
+        HttpMessages.getMessage(200);
+
+    }
+
+
+    /**
+     * Number of bytes last written
+     */
+    protected MutableInteger lastWrite = new MutableInteger(1);
+
+    /**
+     * Underlying socket.
+     */
+    protected NioChannel socket;
+    
+    /**
+     * Selector pool, for blocking reads and blocking writes
+     */
+    protected NioSelectorPool pool;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the underlying socket.
+     */
+    public void setSocket(NioChannel socket) {
+        this.socket = socket;
+    }
+
+    public void setSelectorPool(NioSelectorPool pool) { 
+        this.pool = pool;
+    }
+
+    public NioSelectorPool getSelectorPool() {
+        return pool;
+    }    
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Flush the response.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     * 
+     */
+    @Override
+    public void flush() throws IOException {
+
+        super.flush();
+        // Flush the current buffer
+        flushBuffer();
+
+    }
+
+
+    /**
+     * Recycle the output buffer. This should be called when closing the 
+     * connection.
+     */
+    @Override
+    public void recycle() {
+        super.recycle();
+        if (socket != null) {
+            socket.getBufHandler().getWriteBuffer().clear();
+            socket = null;
+        }
+        lastWrite.set(1);
+    }
+
+
+    /**
+     * End request.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    public void endRequest() throws IOException {
+        super.endRequest();
+        flushBuffer();
+    }
+
+    // ------------------------------------------------ HTTP/1.1 Output Methods
+
+
+    /** 
+     * Send an acknowledgment.
+     */
+    @Override
+    public void sendAck() throws IOException {
+
+        if (!committed) {
+            //Socket.send(socket, Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length) < 0
+            socket.getBufHandler() .getWriteBuffer().put(Constants.ACK_BYTES,0,Constants.ACK_BYTES.length);    
+            writeToSocket(socket.getBufHandler() .getWriteBuffer(),true,true);
+        }
+
+    }
+
+    /**
+     * 
+     * @param bytebuffer ByteBuffer
+     * @param flip boolean
+     * @return int
+     * @throws IOException
+     * TODO Fix non blocking write properly
+     */
+    private synchronized int writeToSocket(ByteBuffer bytebuffer, boolean block, boolean flip) throws IOException {
+        if ( flip ) bytebuffer.flip();
+
+        int written = 0;
+        NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+        if ( att == null ) throw new IOException("Key must be cancelled");
+        long writeTimeout = att.getTimeout();
+        Selector selector = null;
+        try {
+            selector = getSelectorPool().get();
+        } catch ( IOException x ) {
+            //ignore
+        }
+        try {
+            written = getSelectorPool().write(bytebuffer, socket, selector, writeTimeout, block,lastWrite);
+            //make sure we are flushed 
+            do {
+                if (socket.flush(true,selector,writeTimeout,lastWrite)) break;
+            }while ( true );
+        }finally { 
+            if ( selector != null ) getSelectorPool().put(selector);
+        }
+        if ( block ) bytebuffer.clear(); //only clear
+        this.total = 0;
+        return written;
+    } 
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Commit the response.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    protected void commit()
+        throws IOException {
+
+        // The response is now committed
+        committed = true;
+        response.setCommitted(true);
+
+        if (pos > 0) {
+            // Sending the response header buffer
+            addToBB(buf, 0, pos);
+        }
+
+    }
+
+    int total = 0;
+    private synchronized void addToBB(byte[] buf, int offset, int length) throws IOException {
+        while (length > 0) {
+            int thisTime = length;
+            if (socket.getBufHandler().getWriteBuffer().position() ==
+                    socket.getBufHandler().getWriteBuffer().capacity()
+                    || socket.getBufHandler().getWriteBuffer().remaining()==0) {
+                flushBuffer();
+            }
+            if (thisTime > socket.getBufHandler().getWriteBuffer().remaining()) {
+                thisTime = socket.getBufHandler().getWriteBuffer().remaining();
+            }
+            socket.getBufHandler().getWriteBuffer().put(buf, offset, thisTime);
+            length = length - thisTime;
+            offset = offset + thisTime;
+            total += thisTime;
+        }
+        NioEndpoint.KeyAttachment ka = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+        if ( ka!= null ) ka.access();//prevent timeouts for just doing client writes
+    }
+
+
+    /**
+     * Callback to write data from the buffer.
+     */
+    protected void flushBuffer()
+        throws IOException {
+
+        //prevent timeout for async,
+        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+        if (key != null) {
+            NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
+            attach.access();
+        }
+
+        //write to the socket, if there is anything to write
+        if (socket.getBufHandler().getWriteBuffer().position() > 0) {
+            socket.getBufHandler().getWriteBuffer().flip();
+            writeToSocket(socket.getBufHandler().getWriteBuffer(),true, false);
+        }
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer 
+        implements OutputBuffer {
+
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res) 
+            throws IOException {
+
+            int len = chunk.getLength();
+            int start = chunk.getStart();
+            byte[] b = chunk.getBuffer();
+            addToBB(b, start, len);
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalOutputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalOutputBuffer.java
new file mode 100644
index 0000000..a2e3c08
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/InternalOutputBuffer.java
@@ -0,0 +1,260 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Output buffer.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalOutputBuffer extends AbstractOutputBuffer
+    implements ByteChunk.ByteOutputChannel {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Default constructor.
+     */
+    public InternalOutputBuffer(Response response, int headerBufferSize) {
+
+        this.response = response;
+
+        buf = new byte[headerBufferSize];
+
+        outputStreamOutputBuffer = new OutputStreamOutputBuffer();
+
+        filterLibrary = new OutputFilter[0];
+        activeFilters = new OutputFilter[0];
+        lastActiveFilter = -1;
+
+        socketBuffer = new ByteChunk();
+        socketBuffer.setByteOutputChannel(this);
+
+        committed = false;
+        finished = false;
+
+    }
+
+    /**
+     * Underlying output stream.
+     */
+    protected OutputStream outputStream;
+
+
+    /**
+     * Socket buffer.
+     */
+    protected ByteChunk socketBuffer;
+
+
+    /**
+     * Socket buffer (extra buffering to reduce number of packets sent).
+     */
+    protected boolean useSocketBuffer = false;    
+    
+
+    /**
+     * Set the underlying socket output stream.
+     */
+    public void setOutputStream(OutputStream outputStream) {
+
+        // FIXME: Check for null ?
+
+        this.outputStream = outputStream;
+
+    }
+
+
+    /**
+     * Set the socket buffer size.
+     */
+    public void setSocketBuffer(int socketBufferSize) {
+
+        if (socketBufferSize > 500) {
+            useSocketBuffer = true;
+            socketBuffer.allocate(socketBufferSize, socketBufferSize);
+        } else {
+            useSocketBuffer = false;
+        }
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Flush the response.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    public void flush()
+        throws IOException {
+
+        super.flush();
+        
+        // Flush the current buffer
+        if (useSocketBuffer) {
+            socketBuffer.flushBuffer();
+        }
+
+    }
+
+
+    /**
+     * Recycle the output buffer. This should be called when closing the 
+     * connection.
+     */
+    @Override
+    public void recycle() {
+        super.recycle();
+        socketBuffer.recycle();
+        outputStream = null;
+    }
+
+
+    /**
+     * End processing of current HTTP request.
+     * Note: All bytes of the current request should have been already 
+     * consumed. This method only resets all the pointers so that we are ready
+     * to parse the next HTTP request.
+     */
+    @Override
+    public void nextRequest() {
+        super.nextRequest();
+        socketBuffer.recycle();
+    }
+
+
+    /**
+     * End request.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    public void endRequest()
+        throws IOException {
+        super.endRequest();
+        if (useSocketBuffer) {
+            socketBuffer.flushBuffer();
+        }
+    }
+
+
+    // ------------------------------------------------ HTTP/1.1 Output Methods
+
+
+    /**
+     * Send an acknowledgment.
+     */
+    @Override
+    public void sendAck()
+        throws IOException {
+
+        if (!committed)
+            outputStream.write(Constants.ACK_BYTES);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Commit the response.
+     * 
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    protected void commit()
+        throws IOException {
+
+        // The response is now committed
+        committed = true;
+        response.setCommitted(true);
+
+        if (pos > 0) {
+            // Sending the response header buffer
+            if (useSocketBuffer) {
+                socketBuffer.append(buf, 0, pos);
+            } else {
+                outputStream.write(buf, 0, pos);
+            }
+        }
+
+    }
+
+
+    /**
+     * Callback to write data from the buffer.
+     */
+    @Override
+    public void realWriteBytes(byte cbuf[], int off, int len)
+        throws IOException {
+        if (len > 0) {
+            outputStream.write(cbuf, off, len);
+        }
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class OutputStreamOutputBuffer 
+        implements OutputBuffer {
+
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res) 
+            throws IOException {
+
+            int length = chunk.getLength();
+            if (useSocketBuffer) {
+                socketBuffer.append(chunk.getBuffer(), chunk.getStart(), 
+                                    length);
+            } else {
+                outputStream.write(chunk.getBuffer(), chunk.getStart(), 
+                                   length);
+            }
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings.properties
new file mode 100644
index 0000000..b45140e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings.properties
@@ -0,0 +1,40 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+http11protocol.endpoint.starterror=Error starting endpoint
+http11protocol.proto.error=Error reading request, ignored
+http11protocol.proto.ioexception.debug=IOException reading request
+http11protocol.proto.ioexception.info=IOException reading request, ignored
+http11protocol.proto.socketexception.debug=SocketException reading request
+http11protocol.proto.socketexception.info=SocketException reading request, ignored
+http11protocol.start=Starting Coyote HTTP/1.1 on {0}
+
+http11processor.regexp.error=Error parsing regular expression {0}
+http11processor.filter.unknown=Unknown filter {0}
+http11processor.filter.error=Error intializing filter {0}
+http11processor.header.parse=Error parsing HTTP request header
+http11processor.neverused=This method should never be used
+http11processor.request.prepare=Error preparing request
+http11processor.request.process=Error processing request
+http11processor.request.finish=Error finishing request
+http11processor.response.finish=Error finishing response
+http11processor.socket.info=Exception getting socket information
+http11processor.socket.ssl=Exception getting SSL attributes
+http11processor.socket.sslreneg=Exception re-negotiating SSL connection
+http11processor.socket.timeout=Error setting socket timeout
+
+iib.eof.error=Unexpected EOF read on the socket
+iib.requestheadertoolarge.error=Request header is too large
+iib.invalidmethod=Invalid character (CR or LF) found in method name
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_es.properties
new file mode 100644
index 0000000..3c9d805
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_es.properties
@@ -0,0 +1,37 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+http11protocol.endpoint.starterror = Error arrancando punto final (endpoint)
+http11protocol.proto.error = Error leyendo requerimiento, ignorado
+http11protocol.proto.ioexception.debug = IOException leyendo requerimiento
+http11protocol.proto.ioexception.info = IOException leyendo requerimiento, ignorada
+http11protocol.proto.socketexception.debug = SocketException leyendo requerimiento
+http11protocol.proto.socketexception.info = SocketException leyendo requerimiento, ignorada
+http11protocol.start = Arrancando Coyote HTTP/1.1 en puerto {0}
+
+http11processor.regexp.error = Error al analizar expresi\u00F3n regular {0}
+http11processor.filter.unknown = Filtro desconocido {0}
+http11processor.filter.error = Error inicializando filtro {0}
+http11processor.header.parse = Error analizando cabecera de requerimiento HTTP
+http11processor.request.prepare = Error preparando requerimiento
+http11processor.request.process = Error procesando requerimiento
+http11processor.request.finish = Error acabando requerimiento
+http11processor.response.finish = Error acabando respuesta
+http11processor.socket.info = Excepci\u00F3n obteniendo informaci\u00F3n de conector
+http11processor.socket.ssl = Excepci\u00F3n obteniendo atributos SSL
+http11processor.socket.timeout = Error poniendo tiempo agotado para conector
+
+iib.eof.error = Inesperado Fin De Archivo (EOF) le\u00EDdo en el enchufe (socket)
+iib.requestheadertoolarge.error = La cabecera del requerimiento es demasido grande
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_fr.properties
new file mode 100644
index 0000000..cb4e2b3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_fr.properties
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+http11protocol.endpoint.starterror=Erreur au d\u00e9marrage du point de contact
+http11protocol.proto.error=Erreur \u00e0 la lecture de la requ\u00eate, ignor\u00e9
+http11protocol.proto.ioexception.debug=Exception d'entr\u00e9e/sortie (IOException) \u00e0 la lecture de la requ\u00eate
+http11protocol.proto.ioexception.info=Exception d'entr\u00e9e/sortie \u00e0 la lecture de la requ\u00eate, ignor\u00e9
+http11protocol.proto.socketexception.debug=Exception "Socket" (SocketException) \u00e0 la lecture de la requ\u00eate
+http11protocol.proto.socketexception.info=Exception "Socket" (SocketException) \u00e0 la lecture de la requ\u00eate, ignor\u00e9
+http11protocol.start=D\u00e9marrage de Coyote HTTP/1.1 sur {0}
+
+iib.eof.error=Fin de flux (EOF) inattendue \u00e0 la lecture sur la socket
+iib.requestheadertoolarge.error=L'ent\u00eate de requ\u00eate est trop important
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_ja.properties
new file mode 100644
index 0000000..d79a78c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/LocalStrings_ja.properties
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+http11protocol.endpoint.starterror=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u3092\u8d77\u52d5\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+http11protocol.proto.error=\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059\u304c\u3001\u7121\u8996\u3055\u308c\u307e\u3057\u305f
+http11protocol.proto.ioexception.debug=\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306eIOException\u3067\u3059
+http11protocol.proto.ioexception.info=\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306eIOException\u3067\u3059\u304c\u3001\u7121\u8996\u3055\u308c\u307e\u3057\u305f
+http11protocol.proto.socketexception.debug=\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306eSocketException\u3067\u3059
+http11protocol.proto.socketexception.info=\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306eSocketException\u3067\u3059\u304c\u3001\u7121\u8996\u3055\u308c\u307e\u3057\u305f
+http11protocol.start=Coyote HTTP/1.1\u3092 {0} \u3067\u8d77\u52d5\u3057\u307e\u3059
+ 
+iib.eof.error=\u30bd\u30b1\u30c3\u30c8\u304b\u3089\u4e88\u671f\u3057\u306a\u3044EOF\u3092\u8aad\u307f\u8fbc\u307f\u307e\u3057\u305f
+iib.requestheadertoolarge.error=\u30ea\u30af\u30a8\u30b9\u30c8\u30d8\u30c3\u30c0\u304c\u9577\u3059\u304e\u307e\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/OutputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/OutputFilter.java
new file mode 100644
index 0000000..7585859
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/OutputFilter.java
@@ -0,0 +1,77 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11;
+
+import java.io.IOException;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Output filter.
+ * 
+ * @author Remy Maucherat
+ */
+public interface OutputFilter extends OutputBuffer {
+
+
+    /**
+     * Write some bytes.
+     * 
+     * @return number of bytes written by the filter
+     */
+    @Override
+    public int doWrite(ByteChunk chunk, Response unused)
+        throws IOException;
+
+
+    /**
+     * Some filters need additional parameters from the response. All the 
+     * necessary reading can occur in that method, as this method is called
+     * after the response header processing is complete.
+     */
+    public void setResponse(Response response);
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    public void recycle();
+
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    public void setBuffer(OutputBuffer buffer);
+
+
+    /**
+     * End the current request. It is acceptable to write extra bytes using
+     * buffer.doWrite during the execution of this method.
+     * 
+     * @return Should return 0 unless the filter does some content length 
+     * delimitation, in which case the number is the amount of extra bytes or
+     * missing bytes, which would indicate an error. 
+     * Note: It is recommended that extra bytes be swallowed by the filter.
+     */
+    public long end()
+        throws IOException;
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/ExpressionFactoryImpl.java b/bundles/org.apache.tomcat/src/org/apache/el/ExpressionFactoryImpl.java
new file mode 100644
index 0000000..f8bfc55
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/ExpressionFactoryImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el;
+
+import javax.el.ELContext;
+import javax.el.ExpressionFactory;
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.el.lang.ExpressionBuilder;
+import org.apache.el.util.MessageFactory;
+
+
+/**
+ * @see javax.el.ExpressionFactory
+ * 
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: ExpressionFactoryImpl.java,v 1.1 2011/06/28 21:14:12 rherrmann Exp $
+ */
+public class ExpressionFactoryImpl extends ExpressionFactory {
+
+    /**
+     * 
+     */
+    public ExpressionFactoryImpl() {
+        super();
+    }
+
+    @Override
+    public Object coerceToType(Object obj, Class<?> type) {
+        return ELSupport.coerceToType(obj, type);
+    }
+
+    @Override
+    public MethodExpression createMethodExpression(ELContext context,
+            String expression, Class<?> expectedReturnType,
+            Class<?>[] expectedParamTypes) {
+        ExpressionBuilder builder = new ExpressionBuilder(expression, context);
+        return builder.createMethodExpression(expectedReturnType,
+                expectedParamTypes);
+    }
+
+    @Override
+    public ValueExpression createValueExpression(ELContext context,
+            String expression, Class<?> expectedType) {
+        if (expectedType == null) {
+            throw new NullPointerException(MessageFactory
+                    .get("error.value.expectedType"));
+        }
+        ExpressionBuilder builder = new ExpressionBuilder(expression, context);
+        return builder.createValueExpression(expectedType);
+    }
+
+    @Override
+    public ValueExpression createValueExpression(Object instance,
+            Class<?> expectedType) {
+        if (expectedType == null) {
+            throw new NullPointerException(MessageFactory
+                    .get("error.value.expectedType"));
+        }
+        return new ValueExpressionLiteral(instance, expectedType);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/Messages.properties b/bundles/org.apache.tomcat/src/org/apache/el/Messages.properties
new file mode 100644
index 0000000..7fdc6f2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/Messages.properties
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# General Errors
+error.convert=Cannot convert {0} of type {1} to {2}
+error.compare=Cannot compare {0} to {1}
+error.function=Problems calling function ''{0}''
+error.unreachable.base=Target Unreachable, identifier ''{0}'' resolved to null
+error.unreachable.property=Target Unreachable, ''{0}'' returned null
+error.resolver.unhandled=ELResolver did not handle type: {0} with property of ''{1}''
+error.resolver.unhandled.null=ELResolver cannot handle a null base Object with identifier ''{0}''
+
+# ValueExpressionLiteral
+error.value.literal.write=ValueExpression is a literal and not writable: {0}
+
+# ExpressionFactoryImpl
+error.null=Expression cannot be null
+error.mixed=Expression cannot contain both '#{..}' and '${..}' : {0}
+error.method=Not a valid MethodExpression : {0}
+error.method.nullParms=Parameter types cannot be null
+error.value.expectedType=Expected type cannot be null
+
+# ExpressionMediator
+error.eval=Error Evaluating {0} : {1}
+
+# ValueSetVisitor
+error.syntax.set=Illegal Syntax for Set Operation
+
+# ReflectionUtil
+error.method.notfound=Method not found: {0}.{1}({2})
+error.method.ambiguous=Unable to find unambiguous method: {0}.{1}({2})
+error.property.notfound=Property ''{1}'' not found on {0}
+
+# ValidatingVisitor
+error.fnMapper.null=Expression uses functions, but no FunctionMapper was provided
+error.fnMapper.method=Function ''{0}'' not found
+error.fnMapper.paramcount=Function ''{0}'' specifies {1} params, but {2} were declared
+
+# ExpressionImpl
+error.context.null=ELContext was null
+
+# ArrayELResolver
+error.array.outofbounds=Index {0} is out of bounds for array of size {1}
+
+# ListELResolver
+error.list.outofbounds=Index {0} is out of bounds for list of size {1}
+
+# BeanELResolver
+error.property.notfound=Property ''{1}'' not found on type: {0}
+error.property.invocation=Property ''{1}'' threw an exception from type: {0}
+error.property.notreadable=Property ''{1}'' doesn't have a 'get' specified on type: {0}
+error.property.notwritable=Property ''{1}'' doesn't have a 'set' specified on type: {0}
+
+# Parser
+error.identifier.notjava=The identifier [{0}] is not a valid Java identifier as required by section 1.19 of the EL specification (Identifier ::= Java language identifier). This check can be disabled by setting the system property org.apache.el.parser.SKIP_IDENTIFIER_CHECK to true.
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/Messages_es.properties b/bundles/org.apache.tomcat/src/org/apache/el/Messages_es.properties
new file mode 100644
index 0000000..608a6e4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/Messages_es.properties
@@ -0,0 +1,42 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+error.convert = No puedo convertir {0} desde tipo {1} a {2}
+error.compare = No puedo comparar {0} con {1}
+error.function = Problemas llamando a funci\u00F3n ''{0}''
+error.unreachable.base = Objetivo inalcanzable, identificador ''{0}'' resuelto a nulo
+error.unreachable.property = Objetivo inalcanzable, ''{0}'' devolvi\u00F3 nulo
+error.resolver.unhandled = ELResolver no manej\u00F3 el tipo\: {0} con propiedad de ''{1}''
+error.resolver.unhandled.null = ELResolver no puede manejar un Objeto base nulo  con identificador de ''{0}''
+error.value.literal.write = ValueExpression es un literal y no un grabable\: {0}
+error.null = La expresi\u00F3n no puede ser nula
+error.mixed = La expresi\u00F3n no puede contenera la vez '\#{..}' y '${..}' \: {0}
+error.method = No es una MethodExpression v\u00E1lida\: {0}
+error.method.nullParms = Los tipos de par\u00E1metro no pueden ser nulo
+error.value.expectedType = El tipo esperado no puede ser nulo
+error.eval = Error Evaluando {0} \: {1}
+error.syntax.set = Sit\u00E1xis ilegal para Operaci\u00F3n de Poner Valor
+error.method.notfound = M\u00E9todo no hallado\: {0}.{1}({2})
+error.property.notfound = Propiedad ''{1}'' no hallada en {0}
+error.fnMapper.null = La expresi\u00F3n usa funciones, pero no se ha suministrado FunctionMapper
+error.fnMapper.method = Funci\u00F3n "{0}" no hallada
+error.fnMapper.paramcount = La funci\u00F3n ''{0}'' especifica {1} par\u00E9metros, pero {2} fueron declarados
+error.context.null = ELContext era nulo
+error.array.outofbounds = \u00CDndice {0} fuera de l\u00EDmites para arreglo de medida {1}
+error.list.outofbounds = \u00CDndice {0} fuera de l\u00EDmites para lista de medida {1}
+error.property.notfound = Propiedad ''{1}'' no hallada en tipo\: {0}
+error.property.invocation = Propiedad ''{1}'' lanz\u00F3 una excepci\u00F3n desde tipo\: {0}
+error.property.notreadable = La propiedad ''{1}'' no tiene un 'get' especificado en el tipo\: {0}
+error.property.notwritable = La propiedad ''{1}'' no tiene un 'set' especificado en el tipo\: {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/MethodExpressionImpl.java b/bundles/org.apache.tomcat/src/org/apache/el/MethodExpressionImpl.java
new file mode 100644
index 0000000..0726bf3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/MethodExpressionImpl.java
@@ -0,0 +1,331 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.FunctionMapper;
+import javax.el.MethodExpression;
+import javax.el.MethodInfo;
+import javax.el.MethodNotFoundException;
+import javax.el.PropertyNotFoundException;
+import javax.el.VariableMapper;
+
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.lang.ExpressionBuilder;
+import org.apache.el.parser.Node;
+import org.apache.el.util.ReflectionUtil;
+
+
+/**
+ * An <code>Expression</code> that refers to a method on an object.
+ * 
+ * <p>
+ * <code>The {@link javax.el.ExpressionFactory#createMethodExpression} method
+ * can be used to parse an expression string and return a concrete instance
+ * of <code>MethodExpression</code> that encapsulates the parsed expression.
+ * The {@link FunctionMapper} is used at parse time, not evaluation time, 
+ * so one is not needed to evaluate an expression using this class.  
+ * However, the {@link ELContext} is needed at evaluation time.</p>
+ *
+ * <p>The {@link #getMethodInfo} and {@link #invoke} methods will evaluate the 
+ * expression each time they are called. The {@link javax.el.ELResolver} in the 
+ * <code>ELContext</code> is used to resolve the top-level variables and to 
+ * determine the behavior of the <code>.</code> and <code>[]</code> 
+ * operators. For any of the two methods, the
+ * {@link javax.el.ELResolver#getValue} method is used to resolve all properties
+ * up to but excluding the last one. This provides the <code>base</code> object
+ * on which the method appears. If the <code>base</code> object is null, a 
+ * <code>NullPointerException</code> must be thrown. At the last resolution, 
+ * the final <code>property</code> is then coerced to a <code>String</code>,
+ * which provides the name of the method to be found. A method matching the 
+ * name and expected parameters provided at parse time is found and it is 
+ * either queried or invoked (depending on the method called on this
+ * <code>MethodExpression</code>).</p>
+ *
+ * <p>See the notes about comparison, serialization and immutability in 
+ * the {@link javax.el.Expression} javadocs.
+ *
+ * @see javax.el.ELResolver
+ * @see javax.el.Expression
+ * @see javax.el.ExpressionFactory
+ * @see javax.el.MethodExpression
+ * 
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: MethodExpressionImpl.java,v 1.1 2011/06/28 21:14:12 rherrmann Exp $
+ */
+public final class MethodExpressionImpl extends MethodExpression implements
+        Externalizable {
+
+    private Class<?> expectedType;
+
+    private String expr;
+
+    private FunctionMapper fnMapper;
+
+    private VariableMapper varMapper;
+
+    private transient Node node;
+
+    private Class<?>[] paramTypes;
+
+    /**
+     * 
+     */
+    public MethodExpressionImpl() {
+        super();
+    }
+
+    /**
+     * @param expr
+     * @param node
+     * @param fnMapper
+     * @param expectedType
+     * @param paramTypes
+     */
+    public MethodExpressionImpl(String expr, Node node,
+            FunctionMapper fnMapper, VariableMapper varMapper,
+            Class<?> expectedType, Class<?>[] paramTypes) {
+        super();
+        this.expr = expr;
+        this.node = node;
+        this.fnMapper = fnMapper;
+        this.varMapper = varMapper;
+        this.expectedType = expectedType;
+        this.paramTypes = paramTypes;
+    }
+
+    /**
+     * Determines whether the specified object is equal to this
+     * <code>Expression</code>.
+     * 
+     * <p>
+     * The result is <code>true</code> if and only if the argument is not
+     * <code>null</code>, is an <code>Expression</code> object that is the
+     * of the same type (<code>ValueExpression</code> or
+     * <code>MethodExpression</code>), and has an identical parsed
+     * representation.
+     * </p>
+     * 
+     * <p>
+     * Note that two expressions can be equal if their expression Strings are
+     * different. For example, <code>${fn1:foo()}</code> and
+     * <code>${fn2:foo()}</code> are equal if their corresponding
+     * <code>FunctionMapper</code>s mapped <code>fn1:foo</code> and
+     * <code>fn2:foo</code> to the same method.
+     * </p>
+     * 
+     * @param obj
+     *            the <code>Object</code> to test for equality.
+     * @return <code>true</code> if <code>obj</code> equals this
+     *         <code>Expression</code>; <code>false</code> otherwise.
+     * @see java.util.Hashtable
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        return (obj instanceof MethodExpressionImpl && obj.hashCode() == this
+                .hashCode());
+    }
+
+    /**
+     * Returns the original String used to create this <code>Expression</code>,
+     * unmodified.
+     * 
+     * <p>
+     * This is used for debugging purposes but also for the purposes of
+     * comparison (e.g. to ensure the expression in a configuration file has not
+     * changed).
+     * </p>
+     * 
+     * <p>
+     * This method does not provide sufficient information to re-create an
+     * expression. Two different expressions can have exactly the same
+     * expression string but different function mappings. Serialization should
+     * be used to save and restore the state of an <code>Expression</code>.
+     * </p>
+     * 
+     * @return The original expression String.
+     * 
+     * @see javax.el.Expression#getExpressionString()
+     */
+    @Override
+    public String getExpressionString() {
+        return this.expr;
+    }
+
+    /**
+     * Evaluates the expression relative to the provided context, and returns
+     * information about the actual referenced method.
+     * 
+     * @param context
+     *            The context of this evaluation
+     * @return an instance of <code>MethodInfo</code> containing information
+     *         about the method the expression evaluated to.
+     * @throws NullPointerException
+     *             if context is <code>null</code> or the base object is
+     *             <code>null</code> on the last resolution.
+     * @throws PropertyNotFoundException
+     *             if one of the property resolutions failed because a specified
+     *             variable or property does not exist or is not readable.
+     * @throws MethodNotFoundException
+     *             if no suitable method can be found.
+     * @throws ELException
+     *             if an exception was thrown while performing property or
+     *             variable resolution. The thrown exception must be included as
+     *             the cause property of this exception, if available.
+     * @see javax.el.MethodExpression#getMethodInfo(javax.el.ELContext)
+     */
+    @Override
+    public MethodInfo getMethodInfo(ELContext context)
+            throws PropertyNotFoundException, MethodNotFoundException,
+            ELException {
+        Node n = this.getNode();
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper);
+        return n.getMethodInfo(ctx, this.paramTypes);
+    }
+
+    /**
+     * @return
+     * @throws ELException
+     */
+    private Node getNode() throws ELException {
+        if (this.node == null) {
+            this.node = ExpressionBuilder.createNode(this.expr);
+        }
+        return this.node;
+    }
+
+    /**
+     * Returns the hash code for this <code>Expression</code>.
+     * 
+     * <p>
+     * See the note in the {@link #equals} method on how two expressions can be
+     * equal if their expression Strings are different. Recall that if two
+     * objects are equal according to the <code>equals(Object)</code> method,
+     * then calling the <code>hashCode</code> method on each of the two
+     * objects must produce the same integer result. Implementations must take
+     * special note and implement <code>hashCode</code> correctly.
+     * </p>
+     * 
+     * @return The hash code for this <code>Expression</code>.
+     * @see #equals
+     * @see java.util.Hashtable
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return this.expr.hashCode();
+    }
+
+    /**
+     * Evaluates the expression relative to the provided context, invokes the
+     * method that was found using the supplied parameters, and returns the
+     * result of the method invocation.
+     * 
+     * @param context
+     *            The context of this evaluation.
+     * @param params
+     *            The parameters to pass to the method, or <code>null</code>
+     *            if no parameters.
+     * @return the result of the method invocation (<code>null</code> if the
+     *         method has a <code>void</code> return type).
+     * @throws NullPointerException
+     *             if context is <code>null</code> or the base object is
+     *             <code>null</code> on the last resolution.
+     * @throws PropertyNotFoundException
+     *             if one of the property resolutions failed because a specified
+     *             variable or property does not exist or is not readable.
+     * @throws MethodNotFoundException
+     *             if no suitable method can be found.
+     * @throws ELException
+     *             if an exception was thrown while performing property or
+     *             variable resolution. The thrown exception must be included as
+     *             the cause property of this exception, if available. If the
+     *             exception thrown is an <code>InvocationTargetException</code>,
+     *             extract its <code>cause</code> and pass it to the
+     *             <code>ELException</code> constructor.
+     * @see javax.el.MethodExpression#invoke(javax.el.ELContext,
+     *      java.lang.Object[])
+     */
+    @Override
+    public Object invoke(ELContext context, Object[] params)
+            throws PropertyNotFoundException, MethodNotFoundException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper);
+        return this.getNode().invoke(ctx, this.paramTypes, params);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
+     */
+    @Override
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.expr = in.readUTF();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+        this.paramTypes = ReflectionUtil.toTypeArray(((String[]) in
+                .readObject()));
+        this.fnMapper = (FunctionMapper) in.readObject();
+        this.varMapper = (VariableMapper) in.readObject();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
+     */
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.expr);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+        out.writeObject(ReflectionUtil.toTypeNameArray(this.paramTypes));
+        out.writeObject(this.fnMapper);
+        out.writeObject(this.varMapper);
+    }
+
+    @Override
+    public boolean isLiteralText() {
+        return false;
+    }
+
+    /**
+     * @since EL 2.2
+     * Note: The spelling mistake is deliberate.
+     * isParmetersProvided()  - Specification definition
+     * isParametersProvided() - Corrected spelling
+     */
+    @Override
+    public boolean isParmetersProvided() {
+        return this.getNode().isParametersProvided();
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/MethodExpressionLiteral.java b/bundles/org.apache.tomcat/src/org/apache/el/MethodExpressionLiteral.java
new file mode 100644
index 0000000..ebe9856
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/MethodExpressionLiteral.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.MethodExpression;
+import javax.el.MethodInfo;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.el.util.ReflectionUtil;
+
+
+public class MethodExpressionLiteral extends MethodExpression implements Externalizable {
+
+    private Class<?> expectedType;
+
+    private String expr;
+
+    private Class<?>[] paramTypes;
+
+    public MethodExpressionLiteral() {
+        // do nothing
+    }
+
+    public MethodExpressionLiteral(String expr, Class<?> expectedType,
+            Class<?>[] paramTypes) {
+        this.expr = expr;
+        this.expectedType = expectedType;
+        this.paramTypes = paramTypes;
+    }
+
+    @Override
+    public MethodInfo getMethodInfo(ELContext context) throws ELException {
+        return new MethodInfo(this.expr, this.expectedType, this.paramTypes);
+    }
+
+    @Override
+    public Object invoke(ELContext context, Object[] params) throws ELException {
+        if (this.expectedType != null) {
+            return ELSupport.coerceToType(this.expr, this.expectedType);
+        } else {
+            return this.expr;
+        }
+    }
+
+    @Override
+    public String getExpressionString() {
+        return this.expr;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return (obj instanceof MethodExpressionLiteral && this.hashCode() == obj.hashCode());
+    }
+
+    @Override
+    public int hashCode() {
+        return this.expr.hashCode();
+    }
+
+    @Override
+    public boolean isLiteralText() {
+        return true;
+    }
+
+    @Override
+    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        this.expr = in.readUTF();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+        this.paramTypes = ReflectionUtil.toTypeArray(((String[]) in
+                .readObject()));
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.expr);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+        out.writeObject(ReflectionUtil.toTypeNameArray(this.paramTypes));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/ValueExpressionImpl.java b/bundles/org.apache.tomcat/src/org/apache/el/ValueExpressionImpl.java
new file mode 100644
index 0000000..564903a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/ValueExpressionImpl.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.FunctionMapper;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.el.ValueExpression;
+import javax.el.ValueReference;
+import javax.el.VariableMapper;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.lang.ExpressionBuilder;
+import org.apache.el.parser.AstLiteralExpression;
+import org.apache.el.parser.Node;
+import org.apache.el.util.ReflectionUtil;
+
+
+/**
+ * An <code>Expression</code> that can get or set a value.
+ * 
+ * <p>
+ * In previous incarnations of this API, expressions could only be read.
+ * <code>ValueExpression</code> objects can now be used both to retrieve a
+ * value and to set a value. Expressions that can have a value set on them are
+ * referred to as l-value expressions. Those that cannot are referred to as
+ * r-value expressions. Not all r-value expressions can be used as l-value
+ * expressions (e.g. <code>"${1+1}"</code> or
+ * <code>"${firstName} ${lastName}"</code>). See the EL Specification for
+ * details. Expressions that cannot be used as l-values must always return
+ * <code>true</code> from <code>isReadOnly()</code>.
+ * </p>
+ * 
+ * <p>
+ * <code>The {@link javax.el.ExpressionFactory#createValueExpression} method
+ * can be used to parse an expression string and return a concrete instance
+ * of <code>ValueExpression</code> that encapsulates the parsed expression.
+ * The {@link FunctionMapper} is used at parse time, not evaluation time, 
+ * so one is not needed to evaluate an expression using this class.  
+ * However, the {@link ELContext} is needed at evaluation time.</p>
+ *
+ * <p>The {@link #getValue}, {@link #setValue}, {@link #isReadOnly} and
+ * {@link #getType} methods will evaluate the expression each time they are
+ * called. The {@link javax.el.ELResolver} in the <code>ELContext</code> is used
+ * to resolve the top-level variables and to determine the behavior of the
+ * <code>.</code> and <code>[]</code> operators. For any of the four methods,
+ * the {@link javax.el.ELResolver#getValue} method is used to resolve all
+ * properties up to but excluding the last one. This provides the
+ * <code>base</code> object. At the last resolution, the
+ * <code>ValueExpression</code> will call the corresponding
+ * {@link javax.el.ELResolver#getValue}, {@link javax.el.ELResolver#setValue},
+ * {@link javax.el.ELResolver#isReadOnly} or {@link javax.el.ELResolver#getType}
+ * method, depending on which was called on the <code>ValueExpression</code>.
+ * </p>
+ *
+ * <p>See the notes about comparison, serialization and immutability in 
+ * the {@link javax.el.Expression} javadocs.
+ *
+ * @see javax.el.ELResolver
+ * @see javax.el.Expression
+ * @see javax.el.ExpressionFactory
+ * @see javax.el.ValueExpression
+ * 
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: ValueExpressionImpl.java,v 1.1 2011/06/28 21:14:12 rherrmann Exp $
+ */
+public final class ValueExpressionImpl extends ValueExpression implements
+        Externalizable {
+
+    private Class<?> expectedType;
+
+    private String expr;
+
+    private FunctionMapper fnMapper;
+
+    private VariableMapper varMapper;
+
+    private transient Node node;
+
+    public ValueExpressionImpl() {
+        super();
+    }
+
+    /**
+     * 
+     */
+    public ValueExpressionImpl(String expr, Node node, FunctionMapper fnMapper,
+            VariableMapper varMapper, Class<?> expectedType) {
+        this.expr = expr;
+        this.node = node;
+        this.fnMapper = fnMapper;
+        this.varMapper = varMapper;
+        this.expectedType = expectedType;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        return (obj instanceof ValueExpressionImpl && obj.hashCode() == this
+                .hashCode());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#getExpectedType()
+     */
+    @Override
+    public Class<?> getExpectedType() {
+        return this.expectedType;
+    }
+
+    /**
+     * Returns the type the result of the expression will be coerced to after
+     * evaluation.
+     * 
+     * @return the <code>expectedType</code> passed to the
+     *         <code>ExpressionFactory.createValueExpression</code> method
+     *         that created this <code>ValueExpression</code>.
+     * 
+     * @see javax.el.Expression#getExpressionString()
+     */
+    @Override
+    public String getExpressionString() {
+        return this.expr;
+    }
+
+    /**
+     * @return
+     * @throws ELException
+     */
+    private Node getNode() throws ELException {
+        if (this.node == null) {
+            this.node = ExpressionBuilder.createNode(this.expr);
+        }
+        return this.node;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#getType(javax.el.ELContext)
+     */
+    @Override
+    public Class<?> getType(ELContext context) throws PropertyNotFoundException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper);
+        return this.getNode().getType(ctx);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#getValue(javax.el.ELContext)
+     */
+    @Override
+    public Object getValue(ELContext context) throws PropertyNotFoundException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper);
+        Object value = this.getNode().getValue(ctx);
+        if (this.expectedType != null) {
+            return ELSupport.coerceToType(value, this.expectedType);
+        }
+        return value;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return this.getNode().hashCode();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#isLiteralText()
+     */
+    @Override
+    public boolean isLiteralText() {
+        try {
+            return this.getNode() instanceof AstLiteralExpression;
+        } catch (ELException ele) {
+            return false;
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#isReadOnly(javax.el.ELContext)
+     */
+    @Override
+    public boolean isReadOnly(ELContext context)
+            throws PropertyNotFoundException, ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper);
+        return this.getNode().isReadOnly(ctx);
+    }
+
+    @Override
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.expr = in.readUTF();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+        this.fnMapper = (FunctionMapper) in.readObject();
+        this.varMapper = (VariableMapper) in.readObject();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#setValue(javax.el.ELContext,
+     *      java.lang.Object)
+     */
+    @Override
+    public void setValue(ELContext context, Object value)
+            throws PropertyNotFoundException, PropertyNotWritableException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper);
+        this.getNode().setValue(ctx, value);
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.expr);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+        out.writeObject(this.fnMapper);
+        out.writeObject(this.varMapper);
+    }
+
+    @Override
+    public String toString() {
+        return "ValueExpression["+this.expr+"]";
+    }
+
+    /**
+     * @since EL 2.2
+     */
+    @Override
+    public ValueReference getValueReference(ELContext context) {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper);
+        return this.getNode().getValueReference(ctx);
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/ValueExpressionLiteral.java b/bundles/org.apache.tomcat/src/org/apache/el/ValueExpressionLiteral.java
new file mode 100644
index 0000000..38d0b8d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/ValueExpressionLiteral.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.PropertyNotWritableException;
+import javax.el.ValueExpression;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.el.util.MessageFactory;
+import org.apache.el.util.ReflectionUtil;
+
+
+public final class ValueExpressionLiteral extends ValueExpression implements
+        Externalizable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Object value;
+
+    private Class<?> expectedType;
+
+    public ValueExpressionLiteral() {
+        super();
+    }
+
+    public ValueExpressionLiteral(Object value, Class<?> expectedType) {
+        this.value = value;
+        this.expectedType = expectedType;
+    }
+
+    @Override
+    public Object getValue(ELContext context) {
+        if (this.expectedType != null) {
+            return ELSupport.coerceToType(this.value, this.expectedType);
+        }
+        return this.value;
+    }
+
+    @Override
+    public void setValue(ELContext context, Object value) {
+        throw new PropertyNotWritableException(MessageFactory.get(
+                "error.value.literal.write", this.value));
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context) {
+        return true;
+    }
+
+    @Override
+    public Class<?> getType(ELContext context) {
+        return (this.value != null) ? this.value.getClass() : null;
+    }
+
+    @Override
+    public Class<?> getExpectedType() {
+        return this.expectedType;
+    }
+
+    @Override
+    public String getExpressionString() {
+        return (this.value != null) ? this.value.toString() : null;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return (obj instanceof ValueExpressionLiteral && this
+                .equals((ValueExpressionLiteral) obj));
+    }
+
+    public boolean equals(ValueExpressionLiteral ve) {
+        return (ve != null && (this.value != null && ve.value != null && (this.value == ve.value || this.value
+                .equals(ve.value))));
+    }
+
+    @Override
+    public int hashCode() {
+        return (this.value != null) ? this.value.hashCode() : 0;
+    }
+
+    @Override
+    public boolean isLiteralText() {
+        return true;
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(this.value);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+    }
+
+    @Override
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.value = in.readObject();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/ELArithmetic.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/ELArithmetic.java
new file mode 100644
index 0000000..df47115
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/ELArithmetic.java
@@ -0,0 +1,418 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.apache.el.util.MessageFactory;
+
+
+/**
+ * A helper class of Arithmetic defined by the EL Specification
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: ELArithmetic.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public abstract class ELArithmetic {
+
+    public static final class BigDecimalDelegate extends ELArithmetic {
+
+        @Override
+        protected Number add(Number num0, Number num1) {
+            return ((BigDecimal) num0).add((BigDecimal) num1);
+        }
+
+        @Override
+        protected Number coerce(Number num) {
+            if (num instanceof BigDecimal)
+                return num;
+            if (num instanceof BigInteger)
+                return new BigDecimal((BigInteger) num);
+            return new BigDecimal(num.doubleValue());
+        }
+
+        @Override
+        protected Number coerce(String str) {
+            return new BigDecimal(str);
+        }
+
+        @Override
+        protected Number divide(Number num0, Number num1) {
+            return ((BigDecimal) num0).divide((BigDecimal) num1,
+                    BigDecimal.ROUND_HALF_UP);
+        }
+
+        @Override
+        protected Number subtract(Number num0, Number num1) {
+            return ((BigDecimal) num0).subtract((BigDecimal) num1);
+        }
+
+        @Override
+        protected Number mod(Number num0, Number num1) {
+            return new Double(num0.doubleValue() % num1.doubleValue());
+        }
+
+        @Override
+        protected Number multiply(Number num0, Number num1) {
+            return ((BigDecimal) num0).multiply((BigDecimal) num1);
+        }
+
+        @Override
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof BigDecimal || obj1 instanceof BigDecimal);
+        }
+    }
+
+    public static final class BigIntegerDelegate extends ELArithmetic {
+
+        @Override
+        protected Number add(Number num0, Number num1) {
+            return ((BigInteger) num0).add((BigInteger) num1);
+        }
+
+        @Override
+        protected Number coerce(Number num) {
+            if (num instanceof BigInteger)
+                return num;
+            return new BigInteger(num.toString());
+        }
+
+        @Override
+        protected Number coerce(String str) {
+            return new BigInteger(str);
+        }
+
+        @Override
+        protected Number divide(Number num0, Number num1) {
+            return (new BigDecimal((BigInteger) num0)).divide(new BigDecimal((BigInteger) num1), BigDecimal.ROUND_HALF_UP);
+        }
+
+        @Override
+        protected Number multiply(Number num0, Number num1) {
+            return ((BigInteger) num0).multiply((BigInteger) num1);
+        }
+
+        @Override
+        protected Number mod(Number num0, Number num1) {
+            return ((BigInteger) num0).mod((BigInteger) num1);
+        }
+
+        @Override
+        protected Number subtract(Number num0, Number num1) {
+            return ((BigInteger) num0).subtract((BigInteger) num1);
+        }
+
+        @Override
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof BigInteger || obj1 instanceof BigInteger);
+        }
+    }
+
+    public static final class DoubleDelegate extends ELArithmetic {
+
+        @Override
+        protected Number add(Number num0, Number num1) {
+            // could only be one of these
+            if (num0 instanceof BigDecimal) {
+                return ((BigDecimal) num0).add(new BigDecimal(num1.doubleValue()));
+            } else if (num1 instanceof BigDecimal) {
+                return ((new BigDecimal(num0.doubleValue()).add((BigDecimal) num1)));
+            }
+            return new Double(num0.doubleValue() + num1.doubleValue());
+        }
+
+        @Override
+        protected Number coerce(Number num) {
+            if (num instanceof Double)
+                return num;
+            if (num instanceof BigInteger)
+                return new BigDecimal((BigInteger) num);
+            return new Double(num.doubleValue());
+        }
+
+        @Override
+        protected Number coerce(String str) {
+            return new Double(str);
+        }
+
+        @Override
+        protected Number divide(Number num0, Number num1) {
+            return new Double(num0.doubleValue() / num1.doubleValue());
+        }
+
+        @Override
+        protected Number mod(Number num0, Number num1) {
+            return new Double(num0.doubleValue() % num1.doubleValue());
+        }
+
+        @Override
+        protected Number subtract(Number num0, Number num1) {
+            // could only be one of these
+            if (num0 instanceof BigDecimal) {
+                return ((BigDecimal) num0).subtract(new BigDecimal(num1.doubleValue()));
+            } else if (num1 instanceof BigDecimal) {
+                return ((new BigDecimal(num0.doubleValue()).subtract((BigDecimal) num1)));
+            }
+            return new Double(num0.doubleValue() - num1.doubleValue());
+        }
+
+        @Override
+        protected Number multiply(Number num0, Number num1) {
+            // could only be one of these
+            if (num0 instanceof BigDecimal) {
+                return ((BigDecimal) num0).multiply(new BigDecimal(num1.doubleValue()));
+            } else if (num1 instanceof BigDecimal) {
+                return ((new BigDecimal(num0.doubleValue()).multiply((BigDecimal) num1)));
+            }
+            return new Double(num0.doubleValue() * num1.doubleValue());
+        }
+
+        @Override
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof Double
+                    || obj1 instanceof Double
+                    || obj0 instanceof Float
+                    || obj1 instanceof Float
+                    || (obj0 instanceof String && ELSupport
+                            .isStringFloat((String) obj0)) || (obj1 instanceof String && ELSupport
+                    .isStringFloat((String) obj1)));
+        }
+    }
+
+    public static final class LongDelegate extends ELArithmetic {
+
+        @Override
+        protected Number add(Number num0, Number num1) {
+            return Long.valueOf(num0.longValue() + num1.longValue());
+        }
+
+        @Override
+        protected Number coerce(Number num) {
+            if (num instanceof Long)
+                return num;
+            return Long.valueOf(num.longValue());
+        }
+
+        @Override
+        protected Number coerce(String str) {
+            return Long.valueOf(str);
+        }
+
+        @Override
+        protected Number divide(Number num0, Number num1) {
+            return Long.valueOf(num0.longValue() / num1.longValue());
+        }
+
+        @Override
+        protected Number mod(Number num0, Number num1) {
+            return Long.valueOf(num0.longValue() % num1.longValue());
+        }
+
+        @Override
+        protected Number subtract(Number num0, Number num1) {
+            return Long.valueOf(num0.longValue() - num1.longValue());
+        }
+
+        @Override
+        protected Number multiply(Number num0, Number num1) {
+            return Long.valueOf(num0.longValue() * num1.longValue());
+        }
+
+        @Override
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof Long || obj1 instanceof Long);
+        }
+    }
+
+    public static final BigDecimalDelegate BIGDECIMAL = new BigDecimalDelegate();
+
+    public static final BigIntegerDelegate BIGINTEGER = new BigIntegerDelegate();
+
+    public static final DoubleDelegate DOUBLE = new DoubleDelegate();
+
+    public static final LongDelegate LONG = new LongDelegate();
+
+    private static final Long ZERO = Long.valueOf(0);
+
+    public static final Number add(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return Long.valueOf(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (DOUBLE.matches(obj0, obj1)) {
+            if (BIGINTEGER.matches(obj0, obj1))
+                delegate = BIGDECIMAL;
+            else
+                delegate = DOUBLE;
+        } else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.add(num0, num1);
+    }
+
+    public static final Number mod(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return Long.valueOf(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = DOUBLE;
+        else if (DOUBLE.matches(obj0, obj1))
+            delegate = DOUBLE;
+        else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.mod(num0, num1);
+    }
+
+    public static final Number subtract(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return Long.valueOf(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (DOUBLE.matches(obj0, obj1)) {
+            if (BIGINTEGER.matches(obj0, obj1))
+                delegate = BIGDECIMAL;
+            else
+                delegate = DOUBLE;
+        } else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.subtract(num0, num1);
+    }
+
+    public static final Number divide(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return ZERO;
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else
+            delegate = DOUBLE;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.divide(num0, num1);
+    }
+
+    public static final Number multiply(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return Long.valueOf(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (DOUBLE.matches(obj0, obj1)) {
+            if (BIGINTEGER.matches(obj0, obj1))
+                delegate = BIGDECIMAL;
+            else
+                delegate = DOUBLE;
+        } else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.multiply(num0, num1);
+    }
+
+    public static final boolean isNumber(final Object obj) {
+        return (obj != null && isNumberType(obj.getClass()));
+    }
+
+    public static final boolean isNumberType(final Class<?> type) {
+        return type == Long.TYPE || type == Double.TYPE ||
+            type == Byte.TYPE || type == Short.TYPE ||
+            type == Integer.TYPE || type == Float.TYPE ||
+            Number.class.isAssignableFrom(type);
+    }
+
+    /**
+     *
+     */
+    protected ELArithmetic() {
+        super();
+    }
+
+    protected abstract Number add(final Number num0, final Number num1);
+
+    protected abstract Number multiply(final Number num0, final Number num1);
+
+    protected abstract Number subtract(final Number num0, final Number num1);
+
+    protected abstract Number mod(final Number num0, final Number num1);
+
+    protected abstract Number coerce(final Number num);
+
+    protected final Number coerce(final Object obj) {
+
+        if (isNumber(obj)) {
+            return coerce((Number) obj);
+        }
+        if (obj instanceof String) {
+            return coerce((String) obj);
+        }
+        if (obj == null || "".equals(obj)) {
+            return coerce(ZERO);
+        }
+
+        if (obj instanceof Character) {
+            return coerce(Short.valueOf((short) ((Character) obj).charValue()));
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                obj, obj.getClass(), "Number"));
+    }
+
+    protected abstract Number coerce(final String str);
+
+    protected abstract Number divide(final Number num0, final Number num1);
+
+    protected abstract boolean matches(final Object obj0, final Object obj1);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/ELSupport.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/ELSupport.java
new file mode 100644
index 0000000..c22d373
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/ELSupport.java
@@ -0,0 +1,482 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import javax.el.ELException;
+
+import org.apache.el.util.MessageFactory;
+
+
+/**
+ * A helper class that implements the EL Specification
+ * 
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: ELSupport.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public class ELSupport {
+
+    private static final Long ZERO = Long.valueOf(0L);
+
+    /**
+     * Compare two objects, after coercing to the same type if appropriate.
+     * 
+     * If the objects are identical, or they are equal according to 
+     * {@link #equals(Object, Object)} then return 0.
+     * 
+     * If either object is a BigDecimal, then coerce both to BigDecimal first.
+     * Similarly for Double(Float), BigInteger, and Long(Integer, Char, Short, Byte).
+     *  
+     * Otherwise, check that the first object is an instance of Comparable, and compare
+     * against the second object. If that is null, return 1, otherwise
+     * return the result of comparing against the second object.
+     * 
+     * Similarly, if the second object is Comparable, if the first is null, return -1,
+     * else return the result of comparing against the first object.
+     * 
+     * A null object is considered as:
+     * <ul>
+     * <li>ZERO when compared with Numbers</li>
+     * <li>the empty string for String compares</li>
+     * <li>Otherwise null is considered to be lower than anything else.</li>
+     * </ul>
+     * 
+     * @param obj0 first object
+     * @param obj1 second object
+     * @return -1, 0, or 1 if this object is less than, equal to, or greater than val.
+     * @throws ELException if neither object is Comparable
+     * @throws ClassCastException if the objects are not mutually comparable
+     */
+    public static final int compare(final Object obj0, final Object obj1)
+            throws ELException {
+        if (obj0 == obj1 || equals(obj0, obj1)) {
+            return 0;
+        }
+        if (isBigDecimalOp(obj0, obj1)) {
+            BigDecimal bd0 = (BigDecimal) coerceToNumber(obj0, BigDecimal.class);
+            BigDecimal bd1 = (BigDecimal) coerceToNumber(obj1, BigDecimal.class);
+            return bd0.compareTo(bd1);
+        }
+        if (isDoubleOp(obj0, obj1)) {
+            Double d0 = (Double) coerceToNumber(obj0, Double.class);
+            Double d1 = (Double) coerceToNumber(obj1, Double.class);
+            return d0.compareTo(d1);
+        }
+        if (isBigIntegerOp(obj0, obj1)) {
+            BigInteger bi0 = (BigInteger) coerceToNumber(obj0, BigInteger.class);
+            BigInteger bi1 = (BigInteger) coerceToNumber(obj1, BigInteger.class);
+            return bi0.compareTo(bi1);
+        }
+        if (isLongOp(obj0, obj1)) {
+            Long l0 = (Long) coerceToNumber(obj0, Long.class);
+            Long l1 = (Long) coerceToNumber(obj1, Long.class);
+            return l0.compareTo(l1);
+        }
+        if (obj0 instanceof String || obj1 instanceof String) {
+            return coerceToString(obj0).compareTo(coerceToString(obj1));
+        }
+        if (obj0 instanceof Comparable<?>) {
+            @SuppressWarnings("unchecked") // checked above
+            final Comparable<Object> comparable = (Comparable<Object>) obj0;
+            return (obj1 != null) ? comparable.compareTo(obj1) : 1;
+        }
+        if (obj1 instanceof Comparable<?>) {
+            @SuppressWarnings("unchecked") // checked above
+            final Comparable<Object> comparable = (Comparable<Object>) obj1;
+            return (obj0 != null) ? -comparable.compareTo(obj0) : -1;
+        }
+        throw new ELException(MessageFactory.get("error.compare", obj0, obj1));
+    }
+
+    /**
+     * Compare two objects for equality, after coercing to the same type if appropriate.
+     * 
+     * If the objects are identical (including both null) return true.
+     * If either object is null, return false.
+     * If either object is Boolean, coerce both to Boolean and check equality.
+     * Similarly for Enum, String, BigDecimal, Double(Float), Long(Integer, Short, Byte, Character)
+     * Otherwise default to using Object.equals().
+     * 
+     * @param obj0 the first object
+     * @param obj1 the second object
+     * @return true if the objects are equal
+     * @throws ELException
+     */
+    public static final boolean equals(final Object obj0, final Object obj1)
+            throws ELException {
+        if (obj0 == obj1) {
+            return true;
+        } else if (obj0 == null || obj1 == null) {
+            return false;
+        } else if (obj0 instanceof Boolean || obj1 instanceof Boolean) {
+            return coerceToBoolean(obj0).equals(coerceToBoolean(obj1));
+        } else if (obj0.getClass().isEnum()) {
+            return obj0.equals(coerceToEnum(obj1, obj0.getClass()));
+        } else if (obj1.getClass().isEnum()) {
+            return obj1.equals(coerceToEnum(obj0, obj1.getClass()));
+        } else if (obj0 instanceof String || obj1 instanceof String) {
+            int lexCompare = coerceToString(obj0).compareTo(coerceToString(obj1));
+            return (lexCompare == 0) ? true : false;
+        }
+        if (isBigDecimalOp(obj0, obj1)) {
+            BigDecimal bd0 = (BigDecimal) coerceToNumber(obj0, BigDecimal.class);
+            BigDecimal bd1 = (BigDecimal) coerceToNumber(obj1, BigDecimal.class);
+            return bd0.equals(bd1);
+        }
+        if (isDoubleOp(obj0, obj1)) {
+            Double d0 = (Double) coerceToNumber(obj0, Double.class);
+            Double d1 = (Double) coerceToNumber(obj1, Double.class);
+            return d0.equals(d1);
+        }
+        if (isBigIntegerOp(obj0, obj1)) {
+            BigInteger bi0 = (BigInteger) coerceToNumber(obj0, BigInteger.class);
+            BigInteger bi1 = (BigInteger) coerceToNumber(obj1, BigInteger.class);
+            return bi0.equals(bi1);
+        }
+        if (isLongOp(obj0, obj1)) {
+            Long l0 = (Long) coerceToNumber(obj0, Long.class);
+            Long l1 = (Long) coerceToNumber(obj1, Long.class);
+            return l0.equals(l1);
+        } else {
+            return obj0.equals(obj1);
+        }
+    }
+
+    // Going to have to have some casts /raw types somewhere so doing it here
+    // keeps them all in one place. There might be a neater / better solution
+    // but I couldn't find it
+    @SuppressWarnings("unchecked")
+    public static final Enum<?> coerceToEnum(final Object obj,
+            @SuppressWarnings("rawtypes") Class type) {
+        if (obj == null || "".equals(obj)) {
+            return null;
+        }
+        if (type.isAssignableFrom(obj.getClass())) {
+            return (Enum<?>) obj;
+        }
+        
+        if (!(obj instanceof String)) {
+            throw new ELException(MessageFactory.get("error.convert",
+                    obj, obj.getClass(), type));
+        }
+
+        Enum<?> result;
+        try {
+             result = Enum.valueOf(type, (String) obj);
+        } catch (IllegalArgumentException iae) {
+            throw new ELException(MessageFactory.get("error.convert",
+                    obj, obj.getClass(), type));
+        }
+        return result;
+    }
+
+    /**
+     * Convert an object to Boolean.
+     * Null and empty string are false.
+     * @param obj the object to convert
+     * @return the Boolean value of the object
+     * @throws ELException if object is not Boolean or String
+     */
+    public static final Boolean coerceToBoolean(final Object obj)
+            throws ELException {
+        if (obj == null || "".equals(obj)) {
+            return Boolean.FALSE;
+        }
+        if (obj instanceof Boolean) {
+            return (Boolean) obj;
+        }
+        if (obj instanceof String) {
+            return Boolean.valueOf((String) obj);
+        }
+
+        throw new ELException(MessageFactory.get("error.convert",
+                obj, obj.getClass(), Boolean.class));
+    }
+
+    public static final Character coerceToCharacter(final Object obj)
+            throws ELException {
+        if (obj == null || "".equals(obj)) {
+            return Character.valueOf((char) 0);
+        }
+        if (obj instanceof String) {
+            return Character.valueOf(((String) obj).charAt(0));
+        }
+        if (ELArithmetic.isNumber(obj)) {
+            return Character.valueOf((char) ((Number) obj).shortValue());
+        }
+        Class<?> objType = obj.getClass();
+        if (obj instanceof Character) {
+            return (Character) obj;
+        }
+
+        throw new ELException(MessageFactory.get("error.convert",
+                obj, objType, Character.class));
+    }
+
+    protected static final Number coerceToNumber(final Number number,
+            final Class<?> type) throws ELException {
+        if (Long.TYPE == type || Long.class.equals(type)) {
+            return Long.valueOf(number.longValue());
+        }
+        if (Double.TYPE == type || Double.class.equals(type)) {
+            return new Double(number.doubleValue());
+        }
+        if (Integer.TYPE == type || Integer.class.equals(type)) {
+            return Integer.valueOf(number.intValue());
+        }
+        if (BigInteger.class.equals(type)) {
+            if (number instanceof BigDecimal) {
+                return ((BigDecimal) number).toBigInteger();
+            }
+            if (number instanceof BigInteger) {
+                return number;
+            }
+            return BigInteger.valueOf(number.longValue());
+        }
+        if (BigDecimal.class.equals(type)) {
+            if (number instanceof BigDecimal) {
+                return number;
+            }
+            if (number instanceof BigInteger) {
+                return new BigDecimal((BigInteger) number);
+            }
+            return new BigDecimal(number.doubleValue());
+        }
+        if (Byte.TYPE == type || Byte.class.equals(type)) {
+            return Byte.valueOf(number.byteValue());
+        }
+        if (Short.TYPE == type || Short.class.equals(type)) {
+            return Short.valueOf(number.shortValue());
+        }
+        if (Float.TYPE == type || Float.class.equals(type)) {
+            return new Float(number.floatValue());
+        }
+        if (Number.class.equals(type)) {
+            return number;
+        }
+
+        throw new ELException(MessageFactory.get("error.convert",
+                number, number.getClass(), type));
+    }
+
+    public static final Number coerceToNumber(final Object obj,
+            final Class<?> type) throws ELException {
+        if (obj == null || "".equals(obj)) {
+            return coerceToNumber(ZERO, type);
+        }
+        if (obj instanceof String) {
+            return coerceToNumber((String) obj, type);
+        }
+        if (ELArithmetic.isNumber(obj)) {
+            return coerceToNumber((Number) obj, type);
+        }
+
+        if (obj instanceof Character) {
+            return coerceToNumber(Short.valueOf((short) ((Character) obj)
+                    .charValue()), type);
+        }
+
+        throw new ELException(MessageFactory.get("error.convert",
+                obj, obj.getClass(), type));
+    }
+
+    protected static final Number coerceToNumber(final String val,
+            final Class<?> type) throws ELException {
+        if (Long.TYPE == type || Long.class.equals(type)) {
+            try {
+                return Long.valueOf(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+        if (Integer.TYPE == type || Integer.class.equals(type)) {
+            try {
+                return Integer.valueOf(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+        if (Double.TYPE == type || Double.class.equals(type)) {
+            try {
+                return Double.valueOf(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+        if (BigInteger.class.equals(type)) {
+            try {
+                return new BigInteger(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+        if (BigDecimal.class.equals(type)) {
+            try {
+                return new BigDecimal(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+        if (Byte.TYPE == type || Byte.class.equals(type)) {
+            try {
+                return Byte.valueOf(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+        if (Short.TYPE == type || Short.class.equals(type)) {
+            try {
+                return Short.valueOf(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+        if (Float.TYPE == type || Float.class.equals(type)) {
+            try {
+                return Float.valueOf(val);
+            } catch (NumberFormatException nfe) {
+                throw new ELException(MessageFactory.get("error.convert",
+                        val, String.class, type));
+            }
+        }
+
+        throw new ELException(MessageFactory.get("error.convert",
+                val, String.class, type));
+    }
+
+    /**
+     * Coerce an object to a string
+     * @param obj
+     * @return the String value of the object
+     */
+    public static final String coerceToString(final Object obj) {
+        if (obj == null) {
+            return "";
+        } else if (obj instanceof String) {
+            return (String) obj;
+        } else if (obj instanceof Enum<?>) {
+            return ((Enum<?>) obj).name();
+        } else {
+            return obj.toString();
+        }
+    }
+
+    public static final Object coerceToType(final Object obj,
+            final Class<?> type) throws ELException {
+        if (type == null || Object.class.equals(type) ||
+                (obj != null && type.isAssignableFrom(obj.getClass()))) {
+            return obj;
+        }
+        if (String.class.equals(type)) {
+            return coerceToString(obj);
+        }
+        if (ELArithmetic.isNumberType(type)) {
+            return coerceToNumber(obj, type);
+        }
+        if (Character.class.equals(type) || Character.TYPE == type) {
+            return coerceToCharacter(obj);
+        }
+        if (Boolean.class.equals(type) || Boolean.TYPE == type) {
+            return coerceToBoolean(obj);
+        }
+        if (type.isEnum()) {
+            return coerceToEnum(obj, type);
+        }
+
+        // new to spec
+        if (obj == null)
+            return null;
+        if (obj instanceof String) {
+            if ("".equals(obj))
+                return null;
+            PropertyEditor editor = PropertyEditorManager.findEditor(type);
+            if (editor != null) {
+                editor.setAsText((String) obj);
+                return editor.getValue();
+            }
+        }
+        throw new ELException(MessageFactory.get("error.convert",
+                obj, obj.getClass(), type));
+    }
+
+    public static final boolean isBigDecimalOp(final Object obj0,
+            final Object obj1) {
+        return (obj0 instanceof BigDecimal || obj1 instanceof BigDecimal);
+    }
+
+    public static final boolean isBigIntegerOp(final Object obj0,
+            final Object obj1) {
+        return (obj0 instanceof BigInteger || obj1 instanceof BigInteger);
+    }
+
+    public static final boolean isDoubleOp(final Object obj0, final Object obj1) {
+        return (obj0 instanceof Double
+                || obj1 instanceof Double
+                || obj0 instanceof Float
+                || obj1 instanceof Float);
+    }
+
+    public static final boolean isLongOp(final Object obj0, final Object obj1) {
+        return (obj0 instanceof Long
+                || obj1 instanceof Long
+                || obj0 instanceof Integer
+                || obj1 instanceof Integer
+                || obj0 instanceof Character
+                || obj1 instanceof Character
+                || obj0 instanceof Short
+                || obj1 instanceof Short
+                || obj0 instanceof Byte
+                || obj1 instanceof Byte);
+    }
+
+    public static final boolean isStringFloat(final String str) {
+        int len = str.length();
+        if (len > 1) {
+            for (int i = 0; i < len; i++) {
+                switch (str.charAt(i)) {
+                case 'E':
+                    return true;
+                case 'e':
+                    return true;
+                case '.':
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 
+     */
+    public ELSupport() {
+        super();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/EvaluationContext.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/EvaluationContext.java
new file mode 100644
index 0000000..412f049
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/EvaluationContext.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import java.util.Locale;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.VariableMapper;
+
+public final class EvaluationContext extends ELContext {
+
+    private final ELContext elContext;
+
+    private final FunctionMapper fnMapper;
+
+    private final VariableMapper varMapper;
+
+    public EvaluationContext(ELContext elContext, FunctionMapper fnMapper,
+            VariableMapper varMapper) {
+        this.elContext = elContext;
+        this.fnMapper = fnMapper;
+        this.varMapper = varMapper;
+    }
+
+    public ELContext getELContext() {
+        return this.elContext;
+    }
+
+    @Override
+    public FunctionMapper getFunctionMapper() {
+        return this.fnMapper;
+    }
+
+    @Override
+    public VariableMapper getVariableMapper() {
+        return this.varMapper;
+    }
+
+    @Override
+    // Can't use Class<?> because API needs to match specification in superclass
+    public Object getContext(@SuppressWarnings("rawtypes") Class key) {
+        return this.elContext.getContext(key);
+    }
+
+    @Override
+    public ELResolver getELResolver() {
+        return this.elContext.getELResolver();
+    }
+
+    @Override
+    public boolean isPropertyResolved() {
+        return this.elContext.isPropertyResolved();
+    }
+
+    @Override
+    // Can't use Class<?> because API needs to match specification in superclass
+    public void putContext(@SuppressWarnings("rawtypes") Class key,
+            Object contextObject) {
+        this.elContext.putContext(key, contextObject);
+    }
+
+    @Override
+    public void setPropertyResolved(boolean resolved) {
+        this.elContext.setPropertyResolved(resolved);
+    }
+
+    @Override
+    public Locale getLocale() {
+        return this.elContext.getLocale();
+        }
+
+    @Override
+    public void setLocale(Locale locale) {
+        this.elContext.setLocale(locale);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/ExpressionBuilder.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/ExpressionBuilder.java
new file mode 100644
index 0000000..a77eca2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/ExpressionBuilder.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.FunctionMapper;
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+import org.apache.el.MethodExpressionImpl;
+import org.apache.el.MethodExpressionLiteral;
+import org.apache.el.ValueExpressionImpl;
+import org.apache.el.parser.AstDeferredExpression;
+import org.apache.el.parser.AstDynamicExpression;
+import org.apache.el.parser.AstFunction;
+import org.apache.el.parser.AstIdentifier;
+import org.apache.el.parser.AstLiteralExpression;
+import org.apache.el.parser.AstValue;
+import org.apache.el.parser.ELParser;
+import org.apache.el.parser.Node;
+import org.apache.el.parser.NodeVisitor;
+import org.apache.el.parser.ParseException;
+import org.apache.el.util.ConcurrentCache;
+import org.apache.el.util.MessageFactory;
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: ExpressionBuilder.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public final class ExpressionBuilder implements NodeVisitor {
+
+    private static final int CACHE_SIZE;
+    private static final String CACHE_SIZE_PROP =
+        "org.apache.el.ExpressionBuilder.CACHE_SIZE";
+
+    static {
+        if (System.getSecurityManager() == null) {
+            CACHE_SIZE = Integer.parseInt(
+                    System.getProperty(CACHE_SIZE_PROP, "5000"));
+        } else {
+            CACHE_SIZE = AccessController.doPrivileged(
+                    new PrivilegedAction<Integer>() {
+
+                    @Override
+                    public Integer run() {
+                        return Integer.valueOf(
+                                System.getProperty(CACHE_SIZE_PROP, "5000"));
+                    }
+                }).intValue();
+        }
+    }
+
+    private static final ConcurrentCache<String, Node> cache =
+        new ConcurrentCache<String, Node>(CACHE_SIZE);
+
+    private FunctionMapper fnMapper;
+
+    private VariableMapper varMapper;
+
+    private String expression;
+
+    /**
+     *
+     */
+    public ExpressionBuilder(String expression, ELContext ctx)
+            throws ELException {
+        this.expression = expression;
+
+        FunctionMapper ctxFn = ctx.getFunctionMapper();
+        VariableMapper ctxVar = ctx.getVariableMapper();
+
+        if (ctxFn != null) {
+            this.fnMapper = new FunctionMapperFactory(ctxFn);
+        }
+        if (ctxVar != null) {
+            this.varMapper = new VariableMapperFactory(ctxVar);
+        }
+    }
+
+    public static final Node createNode(String expr) throws ELException {
+        Node n = createNodeInternal(expr);
+        return n;
+    }
+
+    private static final Node createNodeInternal(String expr)
+            throws ELException {
+        if (expr == null) {
+            throw new ELException(MessageFactory.get("error.null"));
+        }
+
+        Node n = cache.get(expr);
+        if (n == null) {
+            try {
+                n = (new ELParser(new StringReader(expr)))
+                        .CompositeExpression();
+
+                // validate composite expression
+                int numChildren = n.jjtGetNumChildren();
+                if (numChildren == 1) {
+                    n = n.jjtGetChild(0);
+                } else {
+                    Class<?> type = null;
+                    Node child = null;
+                    for (int i = 0; i < numChildren; i++) {
+                        child = n.jjtGetChild(i);
+                        if (child instanceof AstLiteralExpression)
+                            continue;
+                        if (type == null)
+                            type = child.getClass();
+                        else {
+                            if (!type.equals(child.getClass())) {
+                                throw new ELException(MessageFactory.get(
+                                        "error.mixed", expr));
+                            }
+                        }
+                    }
+                }
+
+                if (n instanceof AstDeferredExpression
+                        || n instanceof AstDynamicExpression) {
+                    n = n.jjtGetChild(0);
+                }
+                cache.put(expr, n);
+            } catch (ParseException pe) {
+                throw new ELException("Error Parsing: " + expr, pe);
+            }
+        }
+        return n;
+    }
+
+    private void prepare(Node node) throws ELException {
+        try {
+            node.accept(this);
+        } catch (Exception e) {
+            if (e instanceof ELException) {
+                throw (ELException) e;
+            } else {
+                throw (new ELException(e));
+            }
+        }
+        if (this.fnMapper instanceof FunctionMapperFactory) {
+            this.fnMapper = ((FunctionMapperFactory) this.fnMapper).create();
+        }
+        if (this.varMapper instanceof VariableMapperFactory) {
+            this.varMapper = ((VariableMapperFactory) this.varMapper).create();
+        }
+    }
+
+    private Node build() throws ELException {
+        Node n = createNodeInternal(this.expression);
+        this.prepare(n);
+        if (n instanceof AstDeferredExpression
+                || n instanceof AstDynamicExpression) {
+            n = n.jjtGetChild(0);
+        }
+        return n;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.sun.el.parser.NodeVisitor#visit(com.sun.el.parser.Node)
+     */
+    @Override
+    public void visit(Node node) throws ELException {
+        if (node instanceof AstFunction) {
+
+            AstFunction funcNode = (AstFunction) node;
+
+            if (this.fnMapper == null) {
+                throw new ELException(MessageFactory.get("error.fnMapper.null"));
+            }
+            Method m = fnMapper.resolveFunction(funcNode.getPrefix(), funcNode
+                    .getLocalName());
+            if (m == null) {
+                throw new ELException(MessageFactory.get(
+                        "error.fnMapper.method", funcNode.getOutputName()));
+            }
+            int pcnt = m.getParameterTypes().length;
+            if (node.jjtGetNumChildren() != pcnt) {
+                throw new ELException(MessageFactory.get(
+                        "error.fnMapper.paramcount", funcNode.getOutputName(),
+                        "" + pcnt, "" + node.jjtGetNumChildren()));
+            }
+        } else if (node instanceof AstIdentifier && this.varMapper != null) {
+            String variable = ((AstIdentifier) node).getImage();
+
+            // simply capture it
+            this.varMapper.resolveVariable(variable);
+        }
+    }
+
+    public ValueExpression createValueExpression(Class<?> expectedType)
+            throws ELException {
+        Node n = this.build();
+        return new ValueExpressionImpl(this.expression, n, this.fnMapper,
+                this.varMapper, expectedType);
+    }
+
+    public MethodExpression createMethodExpression(Class<?> expectedReturnType,
+            Class<?>[] expectedParamTypes) throws ELException {
+        Node n = this.build();
+        if (!n.isParametersProvided() && expectedParamTypes == null) {
+            throw new NullPointerException(MessageFactory
+                    .get("error.method.nullParms"));
+        }
+        if (n instanceof AstValue || n instanceof AstIdentifier) {
+            return new MethodExpressionImpl(expression, n, this.fnMapper,
+                    this.varMapper, expectedReturnType, expectedParamTypes);
+        } else if (n instanceof AstLiteralExpression) {
+            return new MethodExpressionLiteral(expression, expectedReturnType,
+                    expectedParamTypes);
+        } else {
+            throw new ELException("Not a Valid Method Expression: "
+                    + expression);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/FunctionMapperFactory.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/FunctionMapperFactory.java
new file mode 100644
index 0000000..27c5f90
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/FunctionMapperFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import java.lang.reflect.Method;
+
+import javax.el.FunctionMapper;
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: FunctionMapperFactory.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public class FunctionMapperFactory extends FunctionMapper {
+
+    protected FunctionMapperImpl memento = null;
+    protected FunctionMapper target;
+
+    public FunctionMapperFactory(FunctionMapper mapper) {
+        if (mapper == null) {
+            throw new NullPointerException("FunctionMapper target cannot be null");
+        }
+        this.target = mapper;
+    }
+
+
+    /* (non-Javadoc)
+     * @see javax.el.FunctionMapper#resolveFunction(java.lang.String, java.lang.String)
+     */
+    @Override
+    public Method resolveFunction(String prefix, String localName) {
+        if (this.memento == null) {
+            this.memento = new FunctionMapperImpl();
+        }
+        Method m = this.target.resolveFunction(prefix, localName);
+        if (m != null) {
+            this.memento.addFunction(prefix, localName, m);
+        }
+        return m;
+    }
+
+    public FunctionMapper create() {
+        return this.memento;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/FunctionMapperImpl.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/FunctionMapperImpl.java
new file mode 100644
index 0000000..3c019cd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/FunctionMapperImpl.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.el.FunctionMapper;
+
+import org.apache.el.util.ReflectionUtil;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: FunctionMapperImpl.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ */
+public class FunctionMapperImpl extends FunctionMapper implements
+        Externalizable {
+
+    private static final long serialVersionUID = 1L;
+
+    protected Map<String, Function> functions = null;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.FunctionMapper#resolveFunction(java.lang.String,
+     *      java.lang.String)
+     */
+    @Override
+    public Method resolveFunction(String prefix, String localName) {
+        if (this.functions != null) {
+            Function f = this.functions.get(prefix + ":" + localName);
+            return f.getMethod();
+        }
+        return null;
+    }
+
+    public void addFunction(String prefix, String localName, Method m) {
+        if (this.functions == null) {
+            this.functions = new HashMap<String, Function>();
+        }
+        Function f = new Function(prefix, localName, m);
+        synchronized (this) {
+            this.functions.put(prefix+":"+localName, f);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
+     */
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(this.functions);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.functions = (Map<String, Function>) in.readObject();
+    }
+
+    public static class Function implements Externalizable {
+
+        protected transient Method m;
+        protected String owner;
+        protected String name;
+        protected String[] types;
+        protected String prefix;
+        protected String localName;
+
+        /**
+         * 
+         */
+        public Function(String prefix, String localName, Method m) {
+            if (localName == null) {
+                throw new NullPointerException("LocalName cannot be null");
+            }
+            if (m == null) {
+                throw new NullPointerException("Method cannot be null");
+            }
+            this.prefix = prefix;
+            this.localName = localName;
+            this.m = m;
+        }
+
+        public Function() {
+            // for serialization
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
+         */
+        @Override
+        public void writeExternal(ObjectOutput out) throws IOException {
+            out.writeUTF((this.prefix != null) ? this.prefix : "");
+            out.writeUTF(this.localName);
+            // make sure m isn't null
+            getMethod();
+            out.writeUTF((this.owner != null) ?
+                     this.owner :
+                     this.m.getDeclaringClass().getName());
+            out.writeUTF((this.name != null) ?
+                     this.name :
+                     this.m.getName());
+            out.writeObject((this.types != null) ?
+                     this.types :
+                     ReflectionUtil.toTypeNameArray(this.m.getParameterTypes()));
+
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
+         */
+        @Override
+        public void readExternal(ObjectInput in) throws IOException,
+                ClassNotFoundException {
+
+            this.prefix = in.readUTF();
+            if ("".equals(this.prefix)) this.prefix = null;
+            this.localName = in.readUTF();
+            this.owner = in.readUTF();
+            this.name = in.readUTF();
+            this.types = (String[]) in.readObject();
+        }
+
+        public Method getMethod() {
+            if (this.m == null) {
+                try {
+                    Class<?> t = ReflectionUtil.forName(this.owner);
+                    Class<?>[] p = ReflectionUtil.toTypeArray(this.types);
+                    this.m = t.getMethod(this.name, p);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            return this.m;
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof Function) {
+                return this.hashCode() == obj.hashCode();
+            }
+            return false;
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            return (this.prefix + this.localName).hashCode();
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/VariableMapperFactory.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/VariableMapperFactory.java
new file mode 100644
index 0000000..6a937ea
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/VariableMapperFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+public class VariableMapperFactory extends VariableMapper {
+
+    private final VariableMapper target;
+    private VariableMapper momento;
+
+    public VariableMapperFactory(VariableMapper target) {
+        if (target == null) {
+            throw new NullPointerException("Target VariableMapper cannot be null");
+        }
+        this.target = target;
+    }
+
+    public VariableMapper create() {
+        return this.momento;
+    }
+
+    @Override
+    public ValueExpression resolveVariable(String variable) {
+        ValueExpression expr = this.target.resolveVariable(variable);
+        if (expr != null) {
+            if (this.momento == null) {
+                this.momento = new VariableMapperImpl();
+            }
+            this.momento.setVariable(variable, expr);
+        }
+        return expr;
+    }
+
+    @Override
+    public ValueExpression setVariable(String variable, ValueExpression expression) {
+        throw new UnsupportedOperationException("Cannot Set Variables on Factory");
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/lang/VariableMapperImpl.java b/bundles/org.apache.tomcat/src/org/apache/el/lang/VariableMapperImpl.java
new file mode 100644
index 0000000..6ebb362
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/lang/VariableMapperImpl.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.lang;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+public class VariableMapperImpl extends VariableMapper implements Externalizable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Map<String, ValueExpression> vars = new HashMap<String, ValueExpression>();
+
+    public VariableMapperImpl() {
+        super();
+    }
+
+    @Override
+    public ValueExpression resolveVariable(String variable) {
+        return this.vars.get(variable);
+    }
+
+    @Override
+    public ValueExpression setVariable(String variable,
+            ValueExpression expression) {
+        return this.vars.put(variable, expression);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        this.vars = (Map<String, ValueExpression>) in.readObject();
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(this.vars);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ArithmeticNode.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/ArithmeticNode.java
new file mode 100644
index 0000000..bf634ec
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ArithmeticNode.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: ArithmeticNode.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public class ArithmeticNode extends SimpleNode {
+
+    /**
+     * @param i
+     */
+    public ArithmeticNode(int i) {
+        super(i);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return Number.class;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstAnd.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstAnd.java
new file mode 100644
index 0000000..b7e9cc0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstAnd.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstAnd.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstAnd.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstAnd extends BooleanNode {
+    public AstAnd(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = children[0].getValue(ctx);
+        Boolean b = coerceToBoolean(obj);
+        if (!b.booleanValue()) {
+            return b;
+        }
+        obj = children[1].getValue(ctx);
+        b = coerceToBoolean(obj);
+        return b;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstBracketSuffix.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstBracketSuffix.java
new file mode 100644
index 0000000..80df526
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstBracketSuffix.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstBracketSuffix.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstBracketSuffix.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstBracketSuffix extends SimpleNode {
+    public AstBracketSuffix(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getValue(ctx);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstChoice.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstChoice.java
new file mode 100644
index 0000000..3a26682
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstChoice.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstChoice.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstChoice.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstChoice extends SimpleNode {
+    public AstChoice(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        Object val = this.getValue(ctx);
+        return (val != null) ? val.getClass() : null;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Boolean b0 = coerceToBoolean(obj0);
+        return this.children[((b0.booleanValue() ? 1 : 2))].getValue(ctx);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstCompositeExpression.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstCompositeExpression.java
new file mode 100644
index 0000000..a41e676
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstCompositeExpression.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstCompositeExpression.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstCompositeExpression.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstCompositeExpression extends SimpleNode {
+
+    public AstCompositeExpression(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return String.class;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        StringBuilder sb = new StringBuilder(16);
+        Object obj = null;
+        if (this.children != null) {
+            for (int i = 0; i < this.children.length; i++) {
+                obj = this.children[i].getValue(ctx);
+                if (obj != null) {
+                    sb.append(ELSupport.coerceToString(obj));
+                }
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDeferredExpression.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDeferredExpression.java
new file mode 100644
index 0000000..8200d13
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDeferredExpression.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstDeferredExpression.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstDeferredExpression.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstDeferredExpression extends SimpleNode {
+    public AstDeferredExpression(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getType(ctx);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getValue(ctx);
+    }
+
+    @Override
+    public boolean isReadOnly(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].isReadOnly(ctx);
+    }
+
+    @Override
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        this.children[0].setValue(ctx, value);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDiv.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDiv.java
new file mode 100644
index 0000000..034d2fa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDiv.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstDiv.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.ELArithmetic;
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstDiv.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstDiv extends ArithmeticNode {
+    public AstDiv(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        return ELArithmetic.divide(obj0, obj1);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDotSuffix.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDotSuffix.java
new file mode 100644
index 0000000..f43c213
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDotSuffix.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstDotSuffix.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.util.MessageFactory;
+import org.apache.el.util.Validation;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstDotSuffix.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstDotSuffix extends SimpleNode {
+    public AstDotSuffix(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.image;
+    }
+    
+    @Override
+    public void setImage(String image) {
+        if (!Validation.isIdentifier(image)) {
+            throw new ELException(MessageFactory.get("error.identifier.notjava",
+                    image));
+        }
+        this.image = image;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDynamicExpression.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDynamicExpression.java
new file mode 100644
index 0000000..ba8ca73
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstDynamicExpression.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstDynamicExpression.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstDynamicExpression.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstDynamicExpression extends SimpleNode {
+    public AstDynamicExpression(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getType(ctx);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getValue(ctx);
+    }
+
+    @Override
+    public boolean isReadOnly(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].isReadOnly(ctx);
+    }
+
+    @Override
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        this.children[0].setValue(ctx, value);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstEmpty.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstEmpty.java
new file mode 100644
index 0000000..ce62c2f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstEmpty.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstEmpty.java */
+
+package org.apache.el.parser;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstEmpty.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstEmpty extends SimpleNode {
+    public AstEmpty(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.class;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+        if (obj == null) {
+            return Boolean.TRUE;
+        } else if (obj instanceof String) {
+            return Boolean.valueOf(((String) obj).length() == 0);
+        } else if (obj instanceof Object[]) {
+            return Boolean.valueOf(((Object[]) obj).length == 0);
+        } else if (obj instanceof Collection<?>) {
+            return Boolean.valueOf(((Collection<?>) obj).isEmpty());
+        } else if (obj instanceof Map<?,?>) {
+            return Boolean.valueOf(((Map<?,?>) obj).isEmpty());
+        }
+        return Boolean.FALSE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstEqual.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstEqual.java
new file mode 100644
index 0000000..390e2e3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstEqual.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstEqual.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstEqual.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstEqual extends BooleanNode {
+    public AstEqual(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        return Boolean.valueOf(equals(obj0, obj1));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFalse.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFalse.java
new file mode 100644
index 0000000..4038603
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFalse.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstFalse.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstFalse.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstFalse extends BooleanNode {
+    public AstFalse(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.FALSE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFloatingPoint.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFloatingPoint.java
new file mode 100644
index 0000000..ee232ff
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFloatingPoint.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstFloatingPoint.java */
+
+package org.apache.el.parser;
+
+import java.math.BigDecimal;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstFloatingPoint.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstFloatingPoint extends SimpleNode {
+    public AstFloatingPoint(int id) {
+        super(id);
+    }
+
+    private volatile Number number;
+
+    public Number getFloatingPoint() {
+        if (this.number == null) {
+            try {
+                this.number = new Double(this.image);
+            } catch (ArithmeticException e0) {
+                this.number = new BigDecimal(this.image);
+            }
+        }
+        return this.number;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.getFloatingPoint();
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return this.getFloatingPoint().getClass();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFunction.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFunction.java
new file mode 100644
index 0000000..add0ea5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstFunction.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstFunction.java */
+
+package org.apache.el.parser;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.el.ELException;
+import javax.el.FunctionMapper;
+
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.util.MessageFactory;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstFunction.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstFunction extends SimpleNode {
+
+    protected String localName = "";
+
+    protected String prefix = "";
+
+    public AstFunction(int id) {
+        super(id);
+    }
+
+    public String getLocalName() {
+        return localName;
+    }
+
+    public String getOutputName() {
+        if (this.prefix == null) {
+            return this.localName;
+        } else {
+            return this.prefix + ":" + this.localName;
+        }
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        
+        FunctionMapper fnMapper = ctx.getFunctionMapper();
+        
+        // quickly validate again for this request
+        if (fnMapper == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.null"));
+        }
+        Method m = fnMapper.resolveFunction(this.prefix, this.localName);
+        if (m == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.method",
+                    this.getOutputName()));
+        }
+        return m.getReturnType();
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        
+        FunctionMapper fnMapper = ctx.getFunctionMapper();
+        
+        // quickly validate again for this request
+        if (fnMapper == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.null"));
+        }
+        Method m = fnMapper.resolveFunction(this.prefix, this.localName);
+        if (m == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.method",
+                    this.getOutputName()));
+        }
+
+        Class<?>[] paramTypes = m.getParameterTypes();
+        Object[] params = null;
+        Object result = null;
+        int numParams = this.jjtGetNumChildren();
+        if (numParams > 0) {
+            params = new Object[numParams];
+            try {
+                for (int i = 0; i < numParams; i++) {
+                    params[i] = this.children[i].getValue(ctx);
+                    params[i] = coerceToType(params[i], paramTypes[i]);
+                }
+            } catch (ELException ele) {
+                throw new ELException(MessageFactory.get("error.function", this
+                        .getOutputName()), ele);
+            }
+        }
+        try {
+            result = m.invoke(null, params);
+        } catch (IllegalAccessException iae) {
+            throw new ELException(MessageFactory.get("error.function", this
+                    .getOutputName()), iae);
+        } catch (InvocationTargetException ite) {
+            throw new ELException(MessageFactory.get("error.function", this
+                    .getOutputName()), ite.getCause());
+        }
+        return result;
+    }
+
+    public void setLocalName(String localName) {
+        this.localName = localName;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+    
+    
+    @Override
+    public String toString()
+    {
+        return ELParserTreeConstants.jjtNodeName[id] + "[" + this.getOutputName() + "]";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstGreaterThan.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstGreaterThan.java
new file mode 100644
index 0000000..191c155
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstGreaterThan.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstGreaterThan.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstGreaterThan.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstGreaterThan extends BooleanNode {
+    public AstGreaterThan(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == null) {
+            return Boolean.FALSE;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) > 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstGreaterThanEqual.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstGreaterThanEqual.java
new file mode 100644
index 0000000..c95e838
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstGreaterThanEqual.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstGreaterThanEqual.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstGreaterThanEqual.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstGreaterThanEqual extends BooleanNode {
+    public AstGreaterThanEqual(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj0 == obj1) {
+            return Boolean.TRUE;
+        }
+        if (obj0 == null || obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) >= 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstIdentifier.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstIdentifier.java
new file mode 100644
index 0000000..e55630a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstIdentifier.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstIdentifier.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+import javax.el.MethodExpression;
+import javax.el.MethodInfo;
+import javax.el.MethodNotFoundException;
+import javax.el.PropertyNotFoundException;
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.util.MessageFactory;
+import org.apache.el.util.Validation;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstIdentifier.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstIdentifier extends SimpleNode {
+    public AstIdentifier(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx) throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                return expr.getType(ctx.getELContext());
+            }
+        }
+        ctx.setPropertyResolved(false);
+        Class<?> result = ctx.getELResolver().getType(ctx, null, this.image);
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled.null", this.image));
+        }
+        return result;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx) throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                return expr.getValue(ctx.getELContext());
+            }
+        }
+        ctx.setPropertyResolved(false);
+        Object result = ctx.getELResolver().getValue(ctx, null, this.image);
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled.null", this.image));
+        }
+        return result;
+    }
+
+    @Override
+    public boolean isReadOnly(EvaluationContext ctx) throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                return expr.isReadOnly(ctx.getELContext());
+            }
+        }
+        ctx.setPropertyResolved(false);
+        boolean result = ctx.getELResolver().isReadOnly(ctx, null, this.image);
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled.null", this.image));
+        }
+        return result;
+    }
+
+    @Override
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                expr.setValue(ctx.getELContext(), value);
+                return;
+            }
+        }
+        ctx.setPropertyResolved(false);
+        ctx.getELResolver().setValue(ctx, null, this.image, value);
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled.null", this.image));
+        }
+    }
+
+    @Override
+    public Object invoke(EvaluationContext ctx, Class<?>[] paramTypes,
+            Object[] paramValues) throws ELException {
+        return this.getMethodExpression(ctx).invoke(ctx.getELContext(), paramValues);
+    }
+    
+
+    @Override
+    public MethodInfo getMethodInfo(EvaluationContext ctx,
+            Class<?>[] paramTypes) throws ELException {
+        return this.getMethodExpression(ctx).getMethodInfo(ctx.getELContext());
+    }
+
+    @Override
+    public void setImage(String image) {
+        if (!Validation.isIdentifier(image)) {
+            throw new ELException(MessageFactory.get("error.identifier.notjava",
+                    image));
+        }
+        this.image = image;
+    }
+
+    private final MethodExpression getMethodExpression(EvaluationContext ctx)
+            throws ELException {
+        Object obj = null;
+
+        // case A: ValueExpression exists, getValue which must
+        // be a MethodExpression
+        VariableMapper varMapper = ctx.getVariableMapper();
+        ValueExpression ve = null;
+        if (varMapper != null) {
+            ve = varMapper.resolveVariable(this.image);
+            if (ve != null) {
+                obj = ve.getValue(ctx);
+            }
+        }
+
+        // case B: evaluate the identity against the ELResolver, again, must be
+        // a MethodExpression to be able to invoke
+        if (ve == null) {
+            ctx.setPropertyResolved(false);
+            obj = ctx.getELResolver().getValue(ctx, null, this.image);
+        }
+
+        // finally provide helpful hints
+        if (obj instanceof MethodExpression) {
+            return (MethodExpression) obj;
+        } else if (obj == null) {
+            throw new MethodNotFoundException("Identity '" + this.image
+                    + "' was null and was unable to invoke");
+        } else {
+            throw new ELException(
+                    "Identity '"
+                            + this.image
+                            + "' does not reference a MethodExpression instance, returned type: "
+                            + obj.getClass().getName());
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstInteger.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstInteger.java
new file mode 100644
index 0000000..5daa24b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstInteger.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstInteger.java */
+
+package org.apache.el.parser;
+
+import java.math.BigInteger;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstInteger.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstInteger extends SimpleNode {
+    public AstInteger(int id) {
+        super(id);
+    }
+
+    private volatile Number number;
+
+    protected Number getInteger() {
+        if (this.number == null) {
+            try {
+                this.number = new Long(this.image);
+            } catch (ArithmeticException e1) {
+                this.number = new BigInteger(this.image);
+            }
+        }
+        return number;
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return this.getInteger().getClass();
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.getInteger();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLessThan.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLessThan.java
new file mode 100644
index 0000000..de41a9b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLessThan.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstLessThan.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstLessThan.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstLessThan extends BooleanNode {
+    public AstLessThan(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == null) {
+            return Boolean.FALSE;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) < 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLessThanEqual.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLessThanEqual.java
new file mode 100644
index 0000000..5f69911
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLessThanEqual.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstLessThanEqual.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstLessThanEqual.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstLessThanEqual extends BooleanNode {
+    public AstLessThanEqual(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj0 == obj1) {
+            return Boolean.TRUE;
+        }
+        if (obj0 == null || obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) <= 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLiteralExpression.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLiteralExpression.java
new file mode 100644
index 0000000..84ae354
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstLiteralExpression.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstLiteralExpression.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstLiteralExpression.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstLiteralExpression extends SimpleNode {
+    public AstLiteralExpression(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx) throws ELException {
+        return String.class;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx) throws ELException {
+        return this.image;
+    }
+
+    @Override
+    public void setImage(String image) {
+        if (image.indexOf('\\') == -1) {
+            this.image = image;
+            return;
+        }
+        int size = image.length();
+        StringBuilder buf = new StringBuilder(size);
+        for (int i = 0; i < size; i++) {
+            char c = image.charAt(i);
+            if (c == '\\' && i + 2 < size) {
+                char c1 = image.charAt(i + 1);
+                char c2 = image.charAt(i + 2);
+                if ((c1 == '#' || c1 == '$') && c2 == '{')  {
+                    c = c1;
+                    i++;
+                }
+            }
+            buf.append(c);
+        }
+        this.image = buf.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMethodParameters.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMethodParameters.java
new file mode 100644
index 0000000..427ea58
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMethodParameters.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstDotSuffix.java */
+
+package org.apache.el.parser;
+
+import java.util.ArrayList;
+
+import org.apache.el.lang.EvaluationContext;
+
+/**
+ * $Id: AstMethodParameters.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstMethodParameters extends SimpleNode {
+    public AstMethodParameters(int id) {
+        super(id);
+    }
+
+    public Object[] getParameters(EvaluationContext ctx) {
+        ArrayList<Object> params = new ArrayList<Object>();
+        for (int i = 0; i < this.jjtGetNumChildren(); i++) {
+            params.add(this.jjtGetChild(i).getValue(ctx));
+        }
+        return params.toArray(new Object[params.size()]);
+    }
+    
+    public Class<?>[] getParameterTypes(EvaluationContext ctx) {
+        ArrayList<Class<?>> paramTypes = new ArrayList<Class<?>>();
+        for (int i = 0; i < this.jjtGetNumChildren(); i++) {
+            paramTypes.add(this.jjtGetChild(i).getType(ctx));
+        }
+        return paramTypes.toArray(new Class<?>[paramTypes.size()]);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMinus.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMinus.java
new file mode 100644
index 0000000..cdae494
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMinus.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstMinus.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.ELArithmetic;
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstMinus.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstMinus extends ArithmeticNode {
+    public AstMinus(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        return ELArithmetic.subtract(obj0, obj1);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMod.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMod.java
new file mode 100644
index 0000000..48224c8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMod.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstMod.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.ELArithmetic;
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstMod.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstMod extends ArithmeticNode {
+    public AstMod(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        return ELArithmetic.mod(obj0, obj1);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMult.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMult.java
new file mode 100644
index 0000000..0e0ee7c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstMult.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstMult.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.ELArithmetic;
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstMult.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstMult extends ArithmeticNode {
+    public AstMult(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        return ELArithmetic.multiply(obj0, obj1);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNegative.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNegative.java
new file mode 100644
index 0000000..b18fd89
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNegative.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstNegative.java */
+
+package org.apache.el.parser;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstNegative.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstNegative extends SimpleNode {
+    public AstNegative(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return Number.class;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+
+        if (obj == null) {
+            return Long.valueOf(0);
+        }
+        if (obj instanceof BigDecimal) {
+            return ((BigDecimal) obj).negate();
+        }
+        if (obj instanceof BigInteger) {
+            return ((BigInteger) obj).negate();
+        }
+        if (obj instanceof String) {
+            if (isStringFloat((String) obj)) {
+                return new Double(-Double.parseDouble((String) obj));
+            }
+            return Long.valueOf(-Long.parseLong((String) obj));
+        }
+        if (obj instanceof Long) {
+            return Long.valueOf(-((Long) obj).longValue());
+        }
+        if (obj instanceof Double) {
+            return new Double(-((Double) obj).doubleValue());
+        }
+        if (obj instanceof Integer) {
+            return Integer.valueOf(-((Integer) obj).intValue());
+        }
+        if (obj instanceof Float) {
+            return new Float(-((Float) obj).floatValue());
+        }
+        if (obj instanceof Short) {
+            return Short.valueOf((short) -((Short) obj).shortValue());
+        }
+        if (obj instanceof Byte) {
+            return Byte.valueOf((byte) -((Byte) obj).byteValue());
+        }
+        Long num = (Long) coerceToNumber(obj, Long.class);
+        return Long.valueOf(-num.longValue());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNot.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNot.java
new file mode 100644
index 0000000..5827115
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNot.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstNot.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstNot.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstNot extends SimpleNode {
+    public AstNot(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.class;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+        Boolean b = coerceToBoolean(obj);
+        return Boolean.valueOf(!b.booleanValue());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNotEqual.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNotEqual.java
new file mode 100644
index 0000000..51fdf97
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNotEqual.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstNotEqual.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstNotEqual.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstNotEqual extends BooleanNode {
+    public AstNotEqual(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        return Boolean.valueOf(!equals(obj0, obj1));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNull.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNull.java
new file mode 100644
index 0000000..7ed7947
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstNull.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstNull.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstNull.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstNull extends SimpleNode {
+    public AstNull(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return null;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstOr.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstOr.java
new file mode 100644
index 0000000..85971f7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstOr.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstOr.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstOr.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstOr extends BooleanNode {
+    public AstOr(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+        Boolean b = coerceToBoolean(obj);
+        if (b.booleanValue()) {
+            return b;
+        }
+        obj = this.children[1].getValue(ctx);
+        b = coerceToBoolean(obj);
+        return b;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstPlus.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstPlus.java
new file mode 100644
index 0000000..0211dbd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstPlus.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstPlus.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.ELArithmetic;
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstPlus.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstPlus extends ArithmeticNode {
+    public AstPlus(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        Object obj1 = this.children[1].getValue(ctx);
+        return ELArithmetic.add(obj0, obj1);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstString.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstString.java
new file mode 100644
index 0000000..6fc6e5a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstString.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstString.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstString.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstString extends SimpleNode {
+    public AstString(int id) {
+        super(id);
+    }
+
+    private volatile String string;
+
+    public String getString() {
+        if (this.string == null) {
+            this.string = this.image.substring(1, this.image.length() - 1);
+        }
+        return this.string;
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return String.class;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.getString();
+    }
+
+    @Override
+    public void setImage(String image) {
+        if (image.indexOf('\\') == -1) {
+            this.image = image;
+            return;
+        }
+        int size = image.length();
+        StringBuilder buf = new StringBuilder(size);
+        for (int i = 0; i < size; i++) {
+            char c = image.charAt(i);
+            if (c == '\\' && i + 1 < size) {
+                char c1 = image.charAt(i + 1);
+                if (c1 == '\\' || c1 == '"' || c1 == '\'') {
+                    c = c1;
+                    i++;
+                }
+            }
+            buf.append(c);
+        }
+        this.image = buf.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstTrue.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstTrue.java
new file mode 100644
index 0000000..0c9f97e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstTrue.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstTrue.java */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstTrue.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstTrue extends BooleanNode {
+    public AstTrue(int id) {
+        super(id);
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.TRUE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/AstValue.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstValue.java
new file mode 100644
index 0000000..9216c5d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/AstValue.java
@@ -0,0 +1,331 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. AstValue.java */
+
+package org.apache.el.parser;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.MethodInfo;
+import javax.el.PropertyNotFoundException;
+import javax.el.ValueReference;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.util.MessageFactory;
+import org.apache.el.util.ReflectionUtil;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: AstValue.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public final class AstValue extends SimpleNode {
+
+    private static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+
+    protected static final boolean COERCE_TO_ZERO;
+    
+    static {
+        if (IS_SECURITY_ENABLED) {
+            COERCE_TO_ZERO = AccessController.doPrivileged(
+                    new PrivilegedAction<Boolean>(){
+                        @Override
+                        public Boolean run() {
+                            return Boolean.valueOf(System.getProperty(
+                                    "org.apache.el.parser.COERCE_TO_ZERO",
+                                    "true"));
+                        }
+
+                    }
+            ).booleanValue();
+        } else {
+            COERCE_TO_ZERO = Boolean.valueOf(System.getProperty(
+                    "org.apache.el.parser.COERCE_TO_ZERO",
+                    "true")).booleanValue();
+        }
+    }
+
+    protected static class Target {
+        protected Object base;
+
+        protected Object property;
+    }
+
+    public AstValue(int id) {
+        super(id);
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx) throws ELException {
+        Target t = getTarget(ctx);
+        ctx.setPropertyResolved(false);
+        Class<?> result = ctx.getELResolver().getType(ctx, t.base, t.property);
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled", t.base, t.property));            
+        }
+        return result;
+    }
+
+    private final Target getTarget(EvaluationContext ctx) throws ELException {
+        // evaluate expr-a to value-a
+        Object base = this.children[0].getValue(ctx);
+
+        // if our base is null (we know there are more properties to evaluate)
+        if (base == null) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.unreachable.base", this.children[0].getImage()));
+        }
+
+        // set up our start/end
+        Object property = null;
+        int propCount = this.jjtGetNumChildren();
+        
+        if (propCount > 2 &&
+                this.jjtGetChild(propCount - 1) instanceof AstMethodParameters) {
+            // Method call with paramaters. 
+            propCount-=2;
+        } else {
+            propCount--;
+        }
+        int i = 1;
+
+        // evaluate any properties before our target
+        ELResolver resolver = ctx.getELResolver();
+        if (propCount > 1) {
+            while (base != null && i < propCount) {
+                property = this.children[i].getValue(ctx);
+                ctx.setPropertyResolved(false);
+                base = resolver.getValue(ctx, base, property);
+                i++;
+            }
+            // if we are in this block, we have more properties to resolve,
+            // but our base was null
+            if (base == null || property == null) {
+                throw new PropertyNotFoundException(MessageFactory.get(
+                        "error.unreachable.property", property));
+            }
+        }
+
+        property = this.children[i].getValue(ctx);
+
+        if (property == null) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.unreachable.property", this.children[i]));
+        }
+
+        Target t = new Target();
+        t.base = base;
+        t.property = property;
+        return t;
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx) throws ELException {
+        Object base = this.children[0].getValue(ctx);
+        int propCount = this.jjtGetNumChildren();
+        int i = 1;
+        Object suffix = null;
+        ELResolver resolver = ctx.getELResolver();
+        while (base != null && i < propCount) {
+            suffix = this.children[i].getValue(ctx);
+            if (i + 1 < propCount &&
+                    (this.children[i+1] instanceof AstMethodParameters)) {
+                AstMethodParameters mps =
+                    (AstMethodParameters) this.children[i+1];
+                // This is a method
+                base = resolver.invoke(ctx, base, suffix, null,
+                        mps.getParameters(ctx));
+                i+=2;
+            } else {
+                // This is a property
+                if (suffix == null) {
+                    return null;
+                }
+                
+                ctx.setPropertyResolved(false);
+                base = resolver.getValue(ctx, base, suffix);
+                i++;
+            }
+        }
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled", base, suffix));            
+        }
+        return base;
+    }
+
+    @Override
+    public boolean isReadOnly(EvaluationContext ctx) throws ELException {
+        Target t = getTarget(ctx);
+        ctx.setPropertyResolved(false);
+        boolean result =
+            ctx.getELResolver().isReadOnly(ctx, t.base, t.property);
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled", t.base, t.property));            
+        }
+        return result;
+    }
+
+    @Override
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        Target t = getTarget(ctx);
+        ctx.setPropertyResolved(false);
+        ELResolver resolver = ctx.getELResolver();
+
+        // coerce to the expected type
+        Class<?> targetClass = resolver.getType(ctx, t.base, t.property);
+        if (COERCE_TO_ZERO == true
+                || !isAssignable(value, targetClass)) {
+            resolver.setValue(ctx, t.base, t.property,
+                    ELSupport.coerceToType(value, targetClass));
+        } else {
+            resolver.setValue(ctx, t.base, t.property, value);
+        }
+        if (!ctx.isPropertyResolved()) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled", t.base, t.property));            
+        }
+    }
+
+    private boolean isAssignable(Object value, Class<?> targetClass) {
+        if (targetClass == null) {
+            return false;
+        } else if (value != null && targetClass.isPrimitive()) {
+            return false;
+        } else if (value != null && !targetClass.isInstance(value)) {
+            return false;
+        }
+        return true;
+    }
+
+
+    @Override
+    // Interface el.parser.Node uses raw types (and is auto-generated)
+    public MethodInfo getMethodInfo(EvaluationContext ctx, 
+            @SuppressWarnings("rawtypes") Class[] paramTypes)
+            throws ELException {
+        Target t = getTarget(ctx);
+        Method m = ReflectionUtil.getMethod(t.base, t.property, paramTypes);
+        return new MethodInfo(m.getName(), m.getReturnType(), m
+                .getParameterTypes());
+    }
+
+    @Override
+    // Interface el.parser.Node uses a raw type (and is auto-generated)
+    public Object invoke(EvaluationContext ctx, 
+            @SuppressWarnings("rawtypes") Class[] paramTypes,
+            Object[] paramValues) throws ELException {
+        
+        Target t = getTarget(ctx);
+        Method m = null;
+        Object[] values = null;
+        if (isParametersProvided()) {
+            values = ((AstMethodParameters)
+                    this.jjtGetChild(2)).getParameters(ctx);
+            Class<?>[] types = getTypesFromValues(values);
+            m = ReflectionUtil.getMethod(t.base, t.property, types);
+        } else {
+            m = ReflectionUtil.getMethod(t.base, t.property, paramTypes);
+            values = paramValues;
+        }
+        if (m.isVarArgs()) {
+            // May need to convert values
+            values = toVarArgs(values, m);
+        }
+        Object result = null;
+        try {
+            result = m.invoke(t.base, values);
+        } catch (IllegalAccessException iae) {
+            throw new ELException(iae);
+        } catch (IllegalArgumentException iae) {
+            throw new ELException(iae);
+        } catch (InvocationTargetException ite) {
+            throw new ELException(ite.getCause());
+        }
+        return result;
+    }
+
+    private Object[] toVarArgs(Object[] src, Method m) {
+        int paramCount = m.getParameterTypes().length;
+        
+        Object[] dest = new Object[paramCount];
+        Object[] varArgs = (Object[]) Array.newInstance(
+                m.getParameterTypes()[paramCount - 1].getComponentType(),
+                src.length - (paramCount - 1));
+        System.arraycopy(src, 0, dest, 0, paramCount - 1);
+        System.arraycopy(src, paramCount - 1, varArgs, 0,
+                src.length - (paramCount - 1));
+        dest[paramCount - 1] = varArgs;
+        return dest;
+    }
+    
+    private Class<?>[] getTypesFromValues(Object[] values) {
+        if (values == null) {
+            return null;
+        }
+        
+        Class<?> result[] = new Class<?>[values.length];
+        for (int i = 0; i < values.length; i++) {
+            if (values[i] == null) {
+                result[i] = null;
+            } else {
+                result[i] = values[i].getClass();
+            }
+        }
+        return result;
+    }
+
+    
+    /**
+     * @since EL 2.2
+     */
+    @Override
+    public ValueReference getValueReference(EvaluationContext ctx) {
+        // Check this is a reference to a base and a property
+        if (this.children.length > 2 &&
+                this.jjtGetChild(2) instanceof AstMethodParameters) {
+            // This is a method call
+            return null;
+        }
+        Target t = getTarget(ctx);
+        return new ValueReference(t.base, t.property);
+    }
+
+    
+    /**
+     * @since EL 2.2
+     */
+    @Override
+    public boolean isParametersProvided() {
+        if (this.children.length > 2
+                && this.jjtGetChild(2) instanceof AstMethodParameters) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/BooleanNode.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/BooleanNode.java
new file mode 100644
index 0000000..7a3331d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/BooleanNode.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.el.parser;
+
+import javax.el.ELException;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: BooleanNode.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public class BooleanNode extends SimpleNode {
+    /**
+     * @param i
+     */
+    public BooleanNode(int i) {
+        super(i);
+    }
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.class;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.html b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.html
new file mode 100644
index 0000000..24a312a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.html
@@ -0,0 +1,223 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<HEAD>
+<TITLE>BNF for ELParser.jj</TITLE>
+</HEAD>
+<BODY>
+<H1 ALIGN=CENTER>BNF for ELParser.jj</H1>
+<H2 ALIGN=CENTER>NON-TERMINALS</H2>
+<TABLE>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod1">CompositeExpression</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>( <A HREF="#prod2">DeferredExpression</A> | <A HREF="#prod3">DynamicExpression</A> | <A HREF="#prod4">LiteralExpression</A> )* &lt;EOF&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod4">LiteralExpression</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;LITERAL_EXPRESSION&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod2">DeferredExpression</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;START_DEFERRED_EXPRESSION&gt; <A HREF="#prod5">Expression</A> &lt;END_EXPRESSION&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod3">DynamicExpression</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;START_DYNAMIC_EXPRESSION&gt; <A HREF="#prod5">Expression</A> &lt;END_EXPRESSION&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod5">Expression</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod6">Choice</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod6">Choice</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod7">Or</A> ( &lt;QUESTIONMARK&gt; <A HREF="#prod6">Choice</A> &lt;COLON&gt; <A HREF="#prod6">Choice</A> )*</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod7">Or</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod8">And</A> ( ( &lt;OR0&gt; | &lt;OR1&gt; ) <A HREF="#prod8">And</A> )*</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod8">And</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod9">Equality</A> ( ( &lt;AND0&gt; | &lt;AND1&gt; ) <A HREF="#prod9">Equality</A> )*</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod9">Equality</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod10">Compare</A> ( ( ( &lt;EQ0&gt; | &lt;EQ1&gt; ) <A HREF="#prod10">Compare</A> ) | ( ( &lt;NE0&gt; | &lt;NE1&gt; ) <A HREF="#prod10">Compare</A> ) )*</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod10">Compare</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod11">Math</A> ( ( ( &lt;LT0&gt; | &lt;LT1&gt; ) <A HREF="#prod11">Math</A> ) | ( ( &lt;GT0&gt; | &lt;GT1&gt; ) <A HREF="#prod11">Math</A> ) | ( ( &lt;LE0&gt; | &lt;LE1&gt; ) <A HREF="#prod11">Math</A> ) | ( ( &lt;GE0&gt; | &lt;GE1&gt; ) <A HREF="#prod11">Math</A> ) )*</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod11">Math</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod12">Multiplication</A> ( ( &lt;PLUS&gt; <A HREF="#prod12">Multiplication</A> ) | ( &lt;MINUS&gt; <A HREF="#prod12">Multiplication</A> ) )*</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod12">Multiplication</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod13">Unary</A> ( ( &lt;MULT&gt; <A HREF="#prod13">Unary</A> ) | ( &lt;DIV&gt; <A HREF="#prod13">Unary</A> ) | ( ( &lt;MOD0&gt; | &lt;MOD1&gt; ) <A HREF="#prod13">Unary</A> ) )*</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod13">Unary</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;MINUS&gt; <A HREF="#prod13">Unary</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>( &lt;NOT0&gt; | &lt;NOT1&gt; ) <A HREF="#prod13">Unary</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;EMPTY&gt; <A HREF="#prod13">Unary</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod14">Value</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod14">Value</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>( <A HREF="#prod15">ValuePrefix</A> ( <A HREF="#prod16">ValueSuffix</A> )* )</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod15">ValuePrefix</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod17">Literal</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod18">NonLiteral</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod16">ValueSuffix</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod19">DotSuffix</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod20">BracketSuffix</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod19">DotSuffix</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;DOT&gt; &lt;IDENTIFIER&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod20">BracketSuffix</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;LBRACK&gt; <A HREF="#prod5">Expression</A> &lt;RBRACK&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod18">NonLiteral</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;LPAREN&gt; <A HREF="#prod5">Expression</A> &lt;RPAREN&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod21">Function</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod22">Identifier</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod22">Identifier</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;IDENTIFIER&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod21">Function</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;IDENTIFIER&gt; ( &lt;FUNCTIONSUFFIX&gt; )? &lt;LPAREN&gt; ( <A HREF="#prod5">Expression</A> ( &lt;COMMA&gt; <A HREF="#prod5">Expression</A> )* )? &lt;RPAREN&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod17">Literal</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod23">Boolean</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod24">FloatingPoint</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod25">Integer</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod26">String</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE><A HREF="#prod27">Null</A></TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod23">Boolean</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;TRUE&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>|</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;FALSE&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod24">FloatingPoint</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;FLOATING_POINT_LITERAL&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod25">Integer</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;INTEGER_LITERAL&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod26">String</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;STRING_LITERAL&gt;</TD>
+</TR>
+<TR>
+<TD ALIGN=RIGHT VALIGN=BASELINE><A NAME="prod27">Null</A></TD>
+<TD ALIGN=CENTER VALIGN=BASELINE>::=</TD>
+<TD ALIGN=LEFT VALIGN=BASELINE>&lt;NULL&gt;</TD>
+</TR>
+</TABLE>
+</BODY>
+</HTML>
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.java
new file mode 100644
index 0000000..c594ec0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.java
@@ -0,0 +1,2263 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. ELParser.java */
+package org.apache.el.parser;
+import java.io.StringReader;
+
+import javax.el.ELException;
+public class ELParser/*@bgen(jjtree)*/implements ELParserTreeConstants, ELParserConstants {/*@bgen(jjtree)*/
+  protected JJTELParserState jjtree = new JJTELParserState();public static Node parse(String ref) throws ELException
+    {
+        try {
+            return (new ELParser(new StringReader(ref))).CompositeExpression();
+        } catch (ParseException pe) {
+            throw new ELException(pe.getMessage());
+        }
+    }
+
+/*
+ * CompositeExpression
+ * Allow most flexible parsing, restrict by examining
+ * type of returned node
+ */
+  final public AstCompositeExpression CompositeExpression() throws ParseException {
+                                                                     /*@bgen(jjtree) CompositeExpression */
+  AstCompositeExpression jjtn000 = new AstCompositeExpression(JJTCOMPOSITEEXPRESSION);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      label_1:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LITERAL_EXPRESSION:
+        case START_DYNAMIC_EXPRESSION:
+        case START_DEFERRED_EXPRESSION:
+          ;
+          break;
+        default:
+          jj_la1[0] = jj_gen;
+          break label_1;
+        }
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case START_DEFERRED_EXPRESSION:
+          DeferredExpression();
+          break;
+        case START_DYNAMIC_EXPRESSION:
+          DynamicExpression();
+          break;
+        case LITERAL_EXPRESSION:
+          LiteralExpression();
+          break;
+        default:
+          jj_la1[1] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+      jj_consume_token(0);
+                                                                                jjtree.closeNodeScope(jjtn000, true);
+                                                                                jjtc000 = false;
+                                                                                {if (true) return jjtn000;}
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+/*
+ * LiteralExpression
+ * Non-EL Expression blocks
+ */
+  final public void LiteralExpression() throws ParseException {
+                                               /*@bgen(jjtree) LiteralExpression */
+                                                AstLiteralExpression jjtn000 = new AstLiteralExpression(JJTLITERALEXPRESSION);
+                                                boolean jjtc000 = true;
+                                                jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(LITERAL_EXPRESSION);
+                             jjtree.closeNodeScope(jjtn000, true);
+                             jjtc000 = false;
+                             jjtn000.setImage(t.image);
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * DeferredExpression
+ * #{..} Expressions
+ */
+  final public void DeferredExpression() throws ParseException {
+                                                 /*@bgen(jjtree) DeferredExpression */
+  AstDeferredExpression jjtn000 = new AstDeferredExpression(JJTDEFERREDEXPRESSION);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(START_DEFERRED_EXPRESSION);
+      Expression();
+      jj_consume_token(END_EXPRESSION);
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * DynamicExpression
+ * ${..} Expressions
+ */
+  final public void DynamicExpression() throws ParseException {
+                                               /*@bgen(jjtree) DynamicExpression */
+  AstDynamicExpression jjtn000 = new AstDynamicExpression(JJTDYNAMICEXPRESSION);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(START_DYNAMIC_EXPRESSION);
+      Expression();
+      jj_consume_token(END_EXPRESSION);
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * Expression
+ * EL Expression Language Root, goes to Choice
+ */
+  final public void Expression() throws ParseException {
+    Choice();
+  }
+
+/*
+ * Choice
+ * For Choice markup a ? b : c, then Or
+ */
+  final public void Choice() throws ParseException {
+    Or();
+    label_2:
+    while (true) {
+      if (jj_2_1(3)) {
+        ;
+      } else {
+        break label_2;
+      }
+      jj_consume_token(QUESTIONMARK);
+      Choice();
+      jj_consume_token(COLON);
+                                                         AstChoice jjtn001 = new AstChoice(JJTCHOICE);
+                                                         boolean jjtc001 = true;
+                                                         jjtree.openNodeScope(jjtn001);
+      try {
+        Choice();
+      } catch (Throwable jjte001) {
+                                                         if (jjtc001) {
+                                                           jjtree.clearNodeScope(jjtn001);
+                                                           jjtc001 = false;
+                                                         } else {
+                                                           jjtree.popNode();
+                                                         }
+                                                         if (jjte001 instanceof RuntimeException) {
+                                                           {if (true) throw (RuntimeException)jjte001;}
+                                                         }
+                                                         if (jjte001 instanceof ParseException) {
+                                                           {if (true) throw (ParseException)jjte001;}
+                                                         }
+                                                         {if (true) throw (Error)jjte001;}
+      } finally {
+                                                         if (jjtc001) {
+                                                           jjtree.closeNodeScope(jjtn001,  3);
+                                                         }
+      }
+    }
+  }
+
+/*
+ * Or
+ * For 'or' '||', then And
+ */
+  final public void Or() throws ParseException {
+    And();
+    label_3:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case OR0:
+      case OR1:
+        ;
+        break;
+      default:
+        jj_la1[2] = jj_gen;
+        break label_3;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case OR0:
+        jj_consume_token(OR0);
+        break;
+      case OR1:
+        jj_consume_token(OR1);
+        break;
+      default:
+        jj_la1[3] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+                           AstOr jjtn001 = new AstOr(JJTOR);
+                           boolean jjtc001 = true;
+                           jjtree.openNodeScope(jjtn001);
+      try {
+        And();
+      } catch (Throwable jjte001) {
+                           if (jjtc001) {
+                             jjtree.clearNodeScope(jjtn001);
+                             jjtc001 = false;
+                           } else {
+                             jjtree.popNode();
+                           }
+                           if (jjte001 instanceof RuntimeException) {
+                             {if (true) throw (RuntimeException)jjte001;}
+                           }
+                           if (jjte001 instanceof ParseException) {
+                             {if (true) throw (ParseException)jjte001;}
+                           }
+                           {if (true) throw (Error)jjte001;}
+      } finally {
+                           if (jjtc001) {
+                             jjtree.closeNodeScope(jjtn001,  2);
+                           }
+      }
+    }
+  }
+
+/*
+ * And
+ * For 'and' '&&', then Equality
+ */
+  final public void And() throws ParseException {
+    Equality();
+    label_4:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case AND0:
+      case AND1:
+        ;
+        break;
+      default:
+        jj_la1[4] = jj_gen;
+        break label_4;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case AND0:
+        jj_consume_token(AND0);
+        break;
+      case AND1:
+        jj_consume_token(AND1);
+        break;
+      default:
+        jj_la1[5] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+                                  AstAnd jjtn001 = new AstAnd(JJTAND);
+                                  boolean jjtc001 = true;
+                                  jjtree.openNodeScope(jjtn001);
+      try {
+        Equality();
+      } catch (Throwable jjte001) {
+                                  if (jjtc001) {
+                                    jjtree.clearNodeScope(jjtn001);
+                                    jjtc001 = false;
+                                  } else {
+                                    jjtree.popNode();
+                                  }
+                                  if (jjte001 instanceof RuntimeException) {
+                                    {if (true) throw (RuntimeException)jjte001;}
+                                  }
+                                  if (jjte001 instanceof ParseException) {
+                                    {if (true) throw (ParseException)jjte001;}
+                                  }
+                                  {if (true) throw (Error)jjte001;}
+      } finally {
+                                  if (jjtc001) {
+                                    jjtree.closeNodeScope(jjtn001,  2);
+                                  }
+      }
+    }
+  }
+
+/*
+ * Equality
+ * For '==' 'eq' '!=' 'ne', then Compare
+ */
+  final public void Equality() throws ParseException {
+    Compare();
+    label_5:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case EQ0:
+      case EQ1:
+      case NE0:
+      case NE1:
+        ;
+        break;
+      default:
+        jj_la1[6] = jj_gen;
+        break label_5;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case EQ0:
+      case EQ1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case EQ0:
+          jj_consume_token(EQ0);
+          break;
+        case EQ1:
+          jj_consume_token(EQ1);
+          break;
+        default:
+          jj_la1[7] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                         AstEqual jjtn001 = new AstEqual(JJTEQUAL);
+                         boolean jjtc001 = true;
+                         jjtree.openNodeScope(jjtn001);
+        try {
+          Compare();
+        } catch (Throwable jjte001) {
+                         if (jjtc001) {
+                           jjtree.clearNodeScope(jjtn001);
+                           jjtc001 = false;
+                         } else {
+                           jjtree.popNode();
+                         }
+                         if (jjte001 instanceof RuntimeException) {
+                           {if (true) throw (RuntimeException)jjte001;}
+                         }
+                         if (jjte001 instanceof ParseException) {
+                           {if (true) throw (ParseException)jjte001;}
+                         }
+                         {if (true) throw (Error)jjte001;}
+        } finally {
+                         if (jjtc001) {
+                           jjtree.closeNodeScope(jjtn001,  2);
+                         }
+        }
+        break;
+      case NE0:
+      case NE1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case NE0:
+          jj_consume_token(NE0);
+          break;
+        case NE1:
+          jj_consume_token(NE1);
+          break;
+        default:
+          jj_la1[8] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                         AstNotEqual jjtn002 = new AstNotEqual(JJTNOTEQUAL);
+                         boolean jjtc002 = true;
+                         jjtree.openNodeScope(jjtn002);
+        try {
+          Compare();
+        } catch (Throwable jjte002) {
+                         if (jjtc002) {
+                           jjtree.clearNodeScope(jjtn002);
+                           jjtc002 = false;
+                         } else {
+                           jjtree.popNode();
+                         }
+                         if (jjte002 instanceof RuntimeException) {
+                           {if (true) throw (RuntimeException)jjte002;}
+                         }
+                         if (jjte002 instanceof ParseException) {
+                           {if (true) throw (ParseException)jjte002;}
+                         }
+                         {if (true) throw (Error)jjte002;}
+        } finally {
+                         if (jjtc002) {
+                           jjtree.closeNodeScope(jjtn002,  2);
+                         }
+        }
+        break;
+      default:
+        jj_la1[9] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Compare
+ * For a bunch of them, then Math
+ */
+  final public void Compare() throws ParseException {
+    Math();
+    label_6:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case GT0:
+      case GT1:
+      case LT0:
+      case LT1:
+      case GE0:
+      case GE1:
+      case LE0:
+      case LE1:
+        ;
+        break;
+      default:
+        jj_la1[10] = jj_gen;
+        break label_6;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case LT0:
+      case LT1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LT0:
+          jj_consume_token(LT0);
+          break;
+        case LT1:
+          jj_consume_token(LT1);
+          break;
+        default:
+          jj_la1[11] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                         AstLessThan jjtn001 = new AstLessThan(JJTLESSTHAN);
+                         boolean jjtc001 = true;
+                         jjtree.openNodeScope(jjtn001);
+        try {
+          Math();
+        } catch (Throwable jjte001) {
+                         if (jjtc001) {
+                           jjtree.clearNodeScope(jjtn001);
+                           jjtc001 = false;
+                         } else {
+                           jjtree.popNode();
+                         }
+                         if (jjte001 instanceof RuntimeException) {
+                           {if (true) throw (RuntimeException)jjte001;}
+                         }
+                         if (jjte001 instanceof ParseException) {
+                           {if (true) throw (ParseException)jjte001;}
+                         }
+                         {if (true) throw (Error)jjte001;}
+        } finally {
+                         if (jjtc001) {
+                           jjtree.closeNodeScope(jjtn001,  2);
+                         }
+        }
+        break;
+      case GT0:
+      case GT1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case GT0:
+          jj_consume_token(GT0);
+          break;
+        case GT1:
+          jj_consume_token(GT1);
+          break;
+        default:
+          jj_la1[12] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                         AstGreaterThan jjtn002 = new AstGreaterThan(JJTGREATERTHAN);
+                         boolean jjtc002 = true;
+                         jjtree.openNodeScope(jjtn002);
+        try {
+          Math();
+        } catch (Throwable jjte002) {
+                         if (jjtc002) {
+                           jjtree.clearNodeScope(jjtn002);
+                           jjtc002 = false;
+                         } else {
+                           jjtree.popNode();
+                         }
+                         if (jjte002 instanceof RuntimeException) {
+                           {if (true) throw (RuntimeException)jjte002;}
+                         }
+                         if (jjte002 instanceof ParseException) {
+                           {if (true) throw (ParseException)jjte002;}
+                         }
+                         {if (true) throw (Error)jjte002;}
+        } finally {
+                         if (jjtc002) {
+                           jjtree.closeNodeScope(jjtn002,  2);
+                         }
+        }
+        break;
+      case LE0:
+      case LE1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LE0:
+          jj_consume_token(LE0);
+          break;
+        case LE1:
+          jj_consume_token(LE1);
+          break;
+        default:
+          jj_la1[13] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                         AstLessThanEqual jjtn003 = new AstLessThanEqual(JJTLESSTHANEQUAL);
+                         boolean jjtc003 = true;
+                         jjtree.openNodeScope(jjtn003);
+        try {
+          Math();
+        } catch (Throwable jjte003) {
+                         if (jjtc003) {
+                           jjtree.clearNodeScope(jjtn003);
+                           jjtc003 = false;
+                         } else {
+                           jjtree.popNode();
+                         }
+                         if (jjte003 instanceof RuntimeException) {
+                           {if (true) throw (RuntimeException)jjte003;}
+                         }
+                         if (jjte003 instanceof ParseException) {
+                           {if (true) throw (ParseException)jjte003;}
+                         }
+                         {if (true) throw (Error)jjte003;}
+        } finally {
+                         if (jjtc003) {
+                           jjtree.closeNodeScope(jjtn003,  2);
+                         }
+        }
+        break;
+      case GE0:
+      case GE1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case GE0:
+          jj_consume_token(GE0);
+          break;
+        case GE1:
+          jj_consume_token(GE1);
+          break;
+        default:
+          jj_la1[14] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                         AstGreaterThanEqual jjtn004 = new AstGreaterThanEqual(JJTGREATERTHANEQUAL);
+                         boolean jjtc004 = true;
+                         jjtree.openNodeScope(jjtn004);
+        try {
+          Math();
+        } catch (Throwable jjte004) {
+                         if (jjtc004) {
+                           jjtree.clearNodeScope(jjtn004);
+                           jjtc004 = false;
+                         } else {
+                           jjtree.popNode();
+                         }
+                         if (jjte004 instanceof RuntimeException) {
+                           {if (true) throw (RuntimeException)jjte004;}
+                         }
+                         if (jjte004 instanceof ParseException) {
+                           {if (true) throw (ParseException)jjte004;}
+                         }
+                         {if (true) throw (Error)jjte004;}
+        } finally {
+                         if (jjtc004) {
+                           jjtree.closeNodeScope(jjtn004,  2);
+                         }
+        }
+        break;
+      default:
+        jj_la1[15] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Math
+ * For '+' '-', then Multiplication
+ */
+  final public void Math() throws ParseException {
+    Multiplication();
+    label_7:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case PLUS:
+      case MINUS:
+        ;
+        break;
+      default:
+        jj_la1[16] = jj_gen;
+        break label_7;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case PLUS:
+        jj_consume_token(PLUS);
+                  AstPlus jjtn001 = new AstPlus(JJTPLUS);
+                  boolean jjtc001 = true;
+                  jjtree.openNodeScope(jjtn001);
+        try {
+          Multiplication();
+        } catch (Throwable jjte001) {
+                  if (jjtc001) {
+                    jjtree.clearNodeScope(jjtn001);
+                    jjtc001 = false;
+                  } else {
+                    jjtree.popNode();
+                  }
+                  if (jjte001 instanceof RuntimeException) {
+                    {if (true) throw (RuntimeException)jjte001;}
+                  }
+                  if (jjte001 instanceof ParseException) {
+                    {if (true) throw (ParseException)jjte001;}
+                  }
+                  {if (true) throw (Error)jjte001;}
+        } finally {
+                  if (jjtc001) {
+                    jjtree.closeNodeScope(jjtn001,  2);
+                  }
+        }
+        break;
+      case MINUS:
+        jj_consume_token(MINUS);
+                   AstMinus jjtn002 = new AstMinus(JJTMINUS);
+                   boolean jjtc002 = true;
+                   jjtree.openNodeScope(jjtn002);
+        try {
+          Multiplication();
+        } catch (Throwable jjte002) {
+                   if (jjtc002) {
+                     jjtree.clearNodeScope(jjtn002);
+                     jjtc002 = false;
+                   } else {
+                     jjtree.popNode();
+                   }
+                   if (jjte002 instanceof RuntimeException) {
+                     {if (true) throw (RuntimeException)jjte002;}
+                   }
+                   if (jjte002 instanceof ParseException) {
+                     {if (true) throw (ParseException)jjte002;}
+                   }
+                   {if (true) throw (Error)jjte002;}
+        } finally {
+                   if (jjtc002) {
+                     jjtree.closeNodeScope(jjtn002,  2);
+                   }
+        }
+        break;
+      default:
+        jj_la1[17] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Multiplication
+ * For a bunch of them, then Unary
+ */
+  final public void Multiplication() throws ParseException {
+    Unary();
+    label_8:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case MULT:
+      case DIV0:
+      case DIV1:
+      case MOD0:
+      case MOD1:
+        ;
+        break;
+      default:
+        jj_la1[18] = jj_gen;
+        break label_8;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case MULT:
+        jj_consume_token(MULT);
+                  AstMult jjtn001 = new AstMult(JJTMULT);
+                  boolean jjtc001 = true;
+                  jjtree.openNodeScope(jjtn001);
+        try {
+          Unary();
+        } catch (Throwable jjte001) {
+                  if (jjtc001) {
+                    jjtree.clearNodeScope(jjtn001);
+                    jjtc001 = false;
+                  } else {
+                    jjtree.popNode();
+                  }
+                  if (jjte001 instanceof RuntimeException) {
+                    {if (true) throw (RuntimeException)jjte001;}
+                  }
+                  if (jjte001 instanceof ParseException) {
+                    {if (true) throw (ParseException)jjte001;}
+                  }
+                  {if (true) throw (Error)jjte001;}
+        } finally {
+                  if (jjtc001) {
+                    jjtree.closeNodeScope(jjtn001,  2);
+                  }
+        }
+        break;
+      case DIV0:
+      case DIV1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case DIV0:
+          jj_consume_token(DIV0);
+          break;
+        case DIV1:
+          jj_consume_token(DIV1);
+          break;
+        default:
+          jj_la1[19] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                           AstDiv jjtn002 = new AstDiv(JJTDIV);
+                           boolean jjtc002 = true;
+                           jjtree.openNodeScope(jjtn002);
+        try {
+          Unary();
+        } catch (Throwable jjte002) {
+                           if (jjtc002) {
+                             jjtree.clearNodeScope(jjtn002);
+                             jjtc002 = false;
+                           } else {
+                             jjtree.popNode();
+                           }
+                           if (jjte002 instanceof RuntimeException) {
+                             {if (true) throw (RuntimeException)jjte002;}
+                           }
+                           if (jjte002 instanceof ParseException) {
+                             {if (true) throw (ParseException)jjte002;}
+                           }
+                           {if (true) throw (Error)jjte002;}
+        } finally {
+                           if (jjtc002) {
+                             jjtree.closeNodeScope(jjtn002,  2);
+                           }
+        }
+        break;
+      case MOD0:
+      case MOD1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case MOD0:
+          jj_consume_token(MOD0);
+          break;
+        case MOD1:
+          jj_consume_token(MOD1);
+          break;
+        default:
+          jj_la1[20] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                           AstMod jjtn003 = new AstMod(JJTMOD);
+                           boolean jjtc003 = true;
+                           jjtree.openNodeScope(jjtn003);
+        try {
+          Unary();
+        } catch (Throwable jjte003) {
+                           if (jjtc003) {
+                             jjtree.clearNodeScope(jjtn003);
+                             jjtc003 = false;
+                           } else {
+                             jjtree.popNode();
+                           }
+                           if (jjte003 instanceof RuntimeException) {
+                             {if (true) throw (RuntimeException)jjte003;}
+                           }
+                           if (jjte003 instanceof ParseException) {
+                             {if (true) throw (ParseException)jjte003;}
+                           }
+                           {if (true) throw (Error)jjte003;}
+        } finally {
+                           if (jjtc003) {
+                             jjtree.closeNodeScope(jjtn003,  2);
+                           }
+        }
+        break;
+      default:
+        jj_la1[21] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Unary
+ * For '-' '!' 'not' 'empty', then Value
+ */
+  final public void Unary() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case MINUS:
+      jj_consume_token(MINUS);
+                  AstNegative jjtn001 = new AstNegative(JJTNEGATIVE);
+                  boolean jjtc001 = true;
+                  jjtree.openNodeScope(jjtn001);
+      try {
+        Unary();
+      } catch (Throwable jjte001) {
+                  if (jjtc001) {
+                    jjtree.clearNodeScope(jjtn001);
+                    jjtc001 = false;
+                  } else {
+                    jjtree.popNode();
+                  }
+                  if (jjte001 instanceof RuntimeException) {
+                    {if (true) throw (RuntimeException)jjte001;}
+                  }
+                  if (jjte001 instanceof ParseException) {
+                    {if (true) throw (ParseException)jjte001;}
+                  }
+                  {if (true) throw (Error)jjte001;}
+      } finally {
+                  if (jjtc001) {
+                    jjtree.closeNodeScope(jjtn001, true);
+                  }
+      }
+      break;
+    case NOT0:
+    case NOT1:
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case NOT0:
+        jj_consume_token(NOT0);
+        break;
+      case NOT1:
+        jj_consume_token(NOT1);
+        break;
+      default:
+        jj_la1[22] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+                          AstNot jjtn002 = new AstNot(JJTNOT);
+                          boolean jjtc002 = true;
+                          jjtree.openNodeScope(jjtn002);
+      try {
+        Unary();
+      } catch (Throwable jjte002) {
+                          if (jjtc002) {
+                            jjtree.clearNodeScope(jjtn002);
+                            jjtc002 = false;
+                          } else {
+                            jjtree.popNode();
+                          }
+                          if (jjte002 instanceof RuntimeException) {
+                            {if (true) throw (RuntimeException)jjte002;}
+                          }
+                          if (jjte002 instanceof ParseException) {
+                            {if (true) throw (ParseException)jjte002;}
+                          }
+                          {if (true) throw (Error)jjte002;}
+      } finally {
+                          if (jjtc002) {
+                            jjtree.closeNodeScope(jjtn002, true);
+                          }
+      }
+      break;
+    case EMPTY:
+      jj_consume_token(EMPTY);
+                  AstEmpty jjtn003 = new AstEmpty(JJTEMPTY);
+                  boolean jjtc003 = true;
+                  jjtree.openNodeScope(jjtn003);
+      try {
+        Unary();
+      } catch (Throwable jjte003) {
+                  if (jjtc003) {
+                    jjtree.clearNodeScope(jjtn003);
+                    jjtc003 = false;
+                  } else {
+                    jjtree.popNode();
+                  }
+                  if (jjte003 instanceof RuntimeException) {
+                    {if (true) throw (RuntimeException)jjte003;}
+                  }
+                  if (jjte003 instanceof ParseException) {
+                    {if (true) throw (ParseException)jjte003;}
+                  }
+                  {if (true) throw (Error)jjte003;}
+      } finally {
+                  if (jjtc003) {
+                    jjtree.closeNodeScope(jjtn003, true);
+                  }
+      }
+      break;
+    case INTEGER_LITERAL:
+    case FLOATING_POINT_LITERAL:
+    case STRING_LITERAL:
+    case TRUE:
+    case FALSE:
+    case NULL:
+    case LPAREN:
+    case IDENTIFIER:
+      Value();
+      break;
+    default:
+      jj_la1[23] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * Value
+ * Defines Prefix plus zero or more Suffixes
+ */
+  final public void Value() throws ParseException {
+      AstValue jjtn001 = new AstValue(JJTVALUE);
+      boolean jjtc001 = true;
+      jjtree.openNodeScope(jjtn001);
+    try {
+      ValuePrefix();
+      label_9:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case DOT:
+        case LBRACK:
+          ;
+          break;
+        default:
+          jj_la1[24] = jj_gen;
+          break label_9;
+        }
+        ValueSuffix();
+      }
+    } catch (Throwable jjte001) {
+      if (jjtc001) {
+        jjtree.clearNodeScope(jjtn001);
+        jjtc001 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte001 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte001;}
+      }
+      if (jjte001 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte001;}
+      }
+      {if (true) throw (Error)jjte001;}
+    } finally {
+      if (jjtc001) {
+        jjtree.closeNodeScope(jjtn001, jjtree.nodeArity() > 1);
+      }
+    }
+  }
+
+/*
+ * ValuePrefix
+ * For Literals, Variables, and Functions
+ */
+  final public void ValuePrefix() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case INTEGER_LITERAL:
+    case FLOATING_POINT_LITERAL:
+    case STRING_LITERAL:
+    case TRUE:
+    case FALSE:
+    case NULL:
+      Literal();
+      break;
+    case LPAREN:
+    case IDENTIFIER:
+      NonLiteral();
+      break;
+    default:
+      jj_la1[25] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * ValueSuffix
+ * Either dot or bracket notation
+ */
+  final public void ValueSuffix() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case DOT:
+      DotSuffix();
+      break;
+    case LBRACK:
+      BracketSuffix();
+      break;
+    default:
+      jj_la1[26] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case LPAREN:
+      MethodParameters();
+      break;
+    default:
+      jj_la1[27] = jj_gen;
+      ;
+    }
+  }
+
+/*
+ * DotSuffix
+ * Dot Property
+ */
+  final public void DotSuffix() throws ParseException {
+                               /*@bgen(jjtree) DotSuffix */
+                                AstDotSuffix jjtn000 = new AstDotSuffix(JJTDOTSUFFIX);
+                                boolean jjtc000 = true;
+                                jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      jj_consume_token(DOT);
+      t = jj_consume_token(IDENTIFIER);
+                           jjtree.closeNodeScope(jjtn000, true);
+                           jjtc000 = false;
+                           jjtn000.setImage(t.image);
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * BracketSuffix
+ * Sub Expression Suffix
+ */
+  final public void BracketSuffix() throws ParseException {
+                                       /*@bgen(jjtree) BracketSuffix */
+  AstBracketSuffix jjtn000 = new AstBracketSuffix(JJTBRACKETSUFFIX);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(LBRACK);
+      Expression();
+      jj_consume_token(RBRACK);
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * MethodParameters
+ */
+  final public void MethodParameters() throws ParseException {
+                                             /*@bgen(jjtree) MethodParameters */
+  AstMethodParameters jjtn000 = new AstMethodParameters(JJTMETHODPARAMETERS);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(LPAREN);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case INTEGER_LITERAL:
+      case FLOATING_POINT_LITERAL:
+      case STRING_LITERAL:
+      case TRUE:
+      case FALSE:
+      case NULL:
+      case LPAREN:
+      case NOT0:
+      case NOT1:
+      case EMPTY:
+      case MINUS:
+      case IDENTIFIER:
+        Expression();
+        label_10:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case COMMA:
+            ;
+            break;
+          default:
+            jj_la1[28] = jj_gen;
+            break label_10;
+          }
+          jj_consume_token(COMMA);
+          Expression();
+        }
+        break;
+      default:
+        jj_la1[29] = jj_gen;
+        ;
+      }
+      jj_consume_token(RPAREN);
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * NonLiteral
+ * For Grouped Operations, Identifiers, and Functions
+ */
+  final public void NonLiteral() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case LPAREN:
+      jj_consume_token(LPAREN);
+      Expression();
+      jj_consume_token(RPAREN);
+      break;
+    default:
+      jj_la1[30] = jj_gen;
+      if (jj_2_2(2147483647)) {
+        Function();
+      } else {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case IDENTIFIER:
+          Identifier();
+          break;
+        default:
+          jj_la1[31] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+    }
+  }
+
+/*
+ * Identifier
+ * Java Language Identifier
+ */
+  final public void Identifier() throws ParseException {
+                                 /*@bgen(jjtree) Identifier */
+                                  AstIdentifier jjtn000 = new AstIdentifier(JJTIDENTIFIER);
+                                  boolean jjtc000 = true;
+                                  jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(IDENTIFIER);
+                     jjtree.closeNodeScope(jjtn000, true);
+                     jjtc000 = false;
+                     jjtn000.setImage(t.image);
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * Function
+ * Namespace:Name(a,b,c)
+ */
+  final public void Function() throws ParseException {
+ /*@bgen(jjtree) Function */
+    AstFunction jjtn000 = new AstFunction(JJTFUNCTION);
+    boolean jjtc000 = true;
+    jjtree.openNodeScope(jjtn000);Token t0 = null;
+    Token t1 = null;
+    try {
+      if (jj_2_3(2)) {
+        t0 = jj_consume_token(IDENTIFIER);
+        jj_consume_token(COLON);
+      } else {
+        ;
+      }
+      t1 = jj_consume_token(IDENTIFIER);
+        if (t0 != null) {
+            jjtn000.setPrefix(t0.image);
+            jjtn000.setLocalName(t1.image);
+        } else {
+            jjtn000.setLocalName(t1.image);
+        }
+      jj_consume_token(LPAREN);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case INTEGER_LITERAL:
+      case FLOATING_POINT_LITERAL:
+      case STRING_LITERAL:
+      case TRUE:
+      case FALSE:
+      case NULL:
+      case LPAREN:
+      case NOT0:
+      case NOT1:
+      case EMPTY:
+      case MINUS:
+      case IDENTIFIER:
+        Expression();
+        label_11:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case COMMA:
+            ;
+            break;
+          default:
+            jj_la1[32] = jj_gen;
+            break label_11;
+          }
+          jj_consume_token(COMMA);
+          Expression();
+        }
+        break;
+      default:
+        jj_la1[33] = jj_gen;
+        ;
+      }
+      jj_consume_token(RPAREN);
+    } catch (Throwable jjte000) {
+      if (jjtc000) {
+        jjtree.clearNodeScope(jjtn000);
+        jjtc000 = false;
+      } else {
+        jjtree.popNode();
+      }
+      if (jjte000 instanceof RuntimeException) {
+        {if (true) throw (RuntimeException)jjte000;}
+      }
+      if (jjte000 instanceof ParseException) {
+        {if (true) throw (ParseException)jjte000;}
+      }
+      {if (true) throw (Error)jjte000;}
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * Literal
+ * Reserved Keywords
+ */
+  final public void Literal() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case TRUE:
+    case FALSE:
+      Boolean();
+      break;
+    case FLOATING_POINT_LITERAL:
+      FloatingPoint();
+      break;
+    case INTEGER_LITERAL:
+      Integer();
+      break;
+    case STRING_LITERAL:
+      String();
+      break;
+    case NULL:
+      Null();
+      break;
+    default:
+      jj_la1[34] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * Boolean
+ * For 'true' 'false'
+ */
+  final public void Boolean() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case TRUE:
+      AstTrue jjtn001 = new AstTrue(JJTTRUE);
+      boolean jjtc001 = true;
+      jjtree.openNodeScope(jjtn001);
+      try {
+        jj_consume_token(TRUE);
+      } finally {
+      if (jjtc001) {
+        jjtree.closeNodeScope(jjtn001, true);
+      }
+      }
+      break;
+    case FALSE:
+        AstFalse jjtn002 = new AstFalse(JJTFALSE);
+        boolean jjtc002 = true;
+        jjtree.openNodeScope(jjtn002);
+      try {
+        jj_consume_token(FALSE);
+      } finally {
+        if (jjtc002) {
+          jjtree.closeNodeScope(jjtn002, true);
+        }
+      }
+      break;
+    default:
+      jj_la1[35] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * FloatinPoint
+ * For Decimal and Floating Point Literals
+ */
+  final public void FloatingPoint() throws ParseException {
+                                       /*@bgen(jjtree) FloatingPoint */
+                                        AstFloatingPoint jjtn000 = new AstFloatingPoint(JJTFLOATINGPOINT);
+                                        boolean jjtc000 = true;
+                                        jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(FLOATING_POINT_LITERAL);
+                                 jjtree.closeNodeScope(jjtn000, true);
+                                 jjtc000 = false;
+                                 jjtn000.setImage(t.image);
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * Integer
+ * For Simple Numeric Literals
+ */
+  final public void Integer() throws ParseException {
+                           /*@bgen(jjtree) Integer */
+                            AstInteger jjtn000 = new AstInteger(JJTINTEGER);
+                            boolean jjtc000 = true;
+                            jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(INTEGER_LITERAL);
+                          jjtree.closeNodeScope(jjtn000, true);
+                          jjtc000 = false;
+                          jjtn000.setImage(t.image);
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * String
+ * For Quoted Literals
+ */
+  final public void String() throws ParseException {
+                         /*@bgen(jjtree) String */
+                          AstString jjtn000 = new AstString(JJTSTRING);
+                          boolean jjtc000 = true;
+                          jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(STRING_LITERAL);
+                         jjtree.closeNodeScope(jjtn000, true);
+                         jjtc000 = false;
+                         jjtn000.setImage(t.image);
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+/*
+ * Null
+ * For 'null'
+ */
+  final public void Null() throws ParseException {
+                     /*@bgen(jjtree) Null */
+  AstNull jjtn000 = new AstNull(JJTNULL);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(NULL);
+    } finally {
+      if (jjtc000) {
+        jjtree.closeNodeScope(jjtn000, true);
+      }
+    }
+  }
+
+  private boolean jj_2_1(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    try { return !jj_3_1(); }
+    catch(LookaheadSuccess ls) { return true; }
+    finally { jj_save(0, xla); }
+  }
+
+  private boolean jj_2_2(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    try { return !jj_3_2(); }
+    catch(LookaheadSuccess ls) { return true; }
+    finally { jj_save(1, xla); }
+  }
+
+  private boolean jj_2_3(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    try { return !jj_3_3(); }
+    catch(LookaheadSuccess ls) { return true; }
+    finally { jj_save(2, xla); }
+  }
+
+  private boolean jj_3R_13() {
+    if (jj_scan_token(IDENTIFIER)) return true;
+    if (jj_scan_token(COLON)) return true;
+    return false;
+  }
+
+  private boolean jj_3_2() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_13()) jj_scanpos = xsp;
+    if (jj_scan_token(IDENTIFIER)) return true;
+    if (jj_scan_token(LPAREN)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_69() {
+    if (jj_scan_token(IDENTIFIER)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_25() {
+    if (jj_3R_31()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_32()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_59() {
+    if (jj_3R_69()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_34() {
+    if (jj_scan_token(MINUS)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_58() {
+    if (jj_3R_68()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_26() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_33()) {
+    jj_scanpos = xsp;
+    if (jj_3R_34()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_33() {
+    if (jj_scan_token(PLUS)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_57() {
+    if (jj_scan_token(LPAREN)) return true;
+    if (jj_3R_67()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_49() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_57()) {
+    jj_scanpos = xsp;
+    if (jj_3R_58()) {
+    jj_scanpos = xsp;
+    if (jj_3R_59()) return true;
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_66() {
+    if (jj_scan_token(NULL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_21() {
+    if (jj_3R_25()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_26()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_30() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(27)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(28)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_65() {
+    if (jj_scan_token(STRING_LITERAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_29() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(29)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(30)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_28() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(23)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(24)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_22() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_27()) {
+    jj_scanpos = xsp;
+    if (jj_3R_28()) {
+    jj_scanpos = xsp;
+    if (jj_3R_29()) {
+    jj_scanpos = xsp;
+    if (jj_3R_30()) return true;
+    }
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_27() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(25)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(26)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_61() {
+    if (jj_scan_token(LBRACK)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_51() {
+    if (jj_3R_61()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_64() {
+    if (jj_scan_token(INTEGER_LITERAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_19() {
+    if (jj_3R_21()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_22()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_60() {
+    if (jj_scan_token(DOT)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_24() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(33)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(34)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_63() {
+    if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_23() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(31)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(32)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_20() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_23()) {
+    jj_scanpos = xsp;
+    if (jj_3R_24()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_50() {
+    if (jj_3R_60()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_18() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(37)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(38)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_47() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_50()) {
+    jj_scanpos = xsp;
+    if (jj_3R_51()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_71() {
+    if (jj_scan_token(FALSE)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_17() {
+    if (jj_3R_19()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_20()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_44() {
+    if (jj_3R_47()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_70() {
+    if (jj_scan_token(TRUE)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_62() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_70()) {
+    jj_scanpos = xsp;
+    if (jj_3R_71()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_46() {
+    if (jj_3R_49()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_45() {
+    if (jj_3R_48()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_15() {
+    if (jj_3R_17()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_18()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_43() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_45()) {
+    jj_scanpos = xsp;
+    if (jj_3R_46()) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_56() {
+    if (jj_3R_66()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_16() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(39)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(40)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_55() {
+    if (jj_3R_65()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_54() {
+    if (jj_3R_64()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_53() {
+    if (jj_3R_63()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_52() {
+    if (jj_3R_62()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_48() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_52()) {
+    jj_scanpos = xsp;
+    if (jj_3R_53()) {
+    jj_scanpos = xsp;
+    if (jj_3R_54()) {
+    jj_scanpos = xsp;
+    if (jj_3R_55()) {
+    jj_scanpos = xsp;
+    if (jj_3R_56()) return true;
+    }
+    }
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_14() {
+    if (jj_3R_15()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_16()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_42() {
+    if (jj_3R_43()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_44()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3_1() {
+    if (jj_scan_token(QUESTIONMARK)) return true;
+    if (jj_3R_12()) return true;
+    if (jj_scan_token(COLON)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_38() {
+    if (jj_3R_42()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_37() {
+    if (jj_scan_token(EMPTY)) return true;
+    if (jj_3R_31()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_12() {
+    if (jj_3R_14()) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3_1()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_36() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(35)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(36)) return true;
+    }
+    if (jj_3R_31()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_31() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_35()) {
+    jj_scanpos = xsp;
+    if (jj_3R_36()) {
+    jj_scanpos = xsp;
+    if (jj_3R_37()) {
+    jj_scanpos = xsp;
+    if (jj_3R_38()) return true;
+    }
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_35() {
+    if (jj_scan_token(MINUS)) return true;
+    if (jj_3R_31()) return true;
+    return false;
+  }
+
+  private boolean jj_3_3() {
+    if (jj_scan_token(IDENTIFIER)) return true;
+    if (jj_scan_token(COLON)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_68() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3_3()) jj_scanpos = xsp;
+    if (jj_scan_token(IDENTIFIER)) return true;
+    if (jj_scan_token(LPAREN)) return true;
+    return false;
+  }
+
+  private boolean jj_3R_67() {
+    if (jj_3R_12()) return true;
+    return false;
+  }
+
+  private boolean jj_3R_41() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(49)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(50)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_40() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(47)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(48)) return true;
+    }
+    return false;
+  }
+
+  private boolean jj_3R_32() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_39()) {
+    jj_scanpos = xsp;
+    if (jj_3R_40()) {
+    jj_scanpos = xsp;
+    if (jj_3R_41()) return true;
+    }
+    }
+    return false;
+  }
+
+  private boolean jj_3R_39() {
+    if (jj_scan_token(MULT)) return true;
+    return false;
+  }
+
+  /** Generated Token Manager. */
+  public ELParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  /** Current token. */
+  public Token token;
+  /** Next token. */
+  public Token jj_nt;
+  private int jj_ntk;
+  private Token jj_scanpos, jj_lastpos;
+  private int jj_la;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[36];
+  static private int[] jj_la1_0;
+  static private int[] jj_la1_1;
+  static {
+      jj_la1_init_0();
+      jj_la1_init_1();
+   }
+   private static void jj_la1_init_0() {
+      jj_la1_0 = new int[] {0xe,0xe,0x0,0x0,0x0,0x0,0x80000000,0x80000000,0x0,0x80000000,0x7f800000,0x6000000,0x1800000,0x60000000,0x18000000,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x27b00,0x90000,0x27b00,0x90000,0x20000,0x400000,0x27b00,0x20000,0x0,0x400000,0x27b00,0x7b00,0x3000,};
+   }
+   private static void jj_la1_init_1() {
+      jj_la1_1 = new int[] {0x0,0x0,0x180,0x180,0x60,0x60,0x7,0x1,0x6,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x3000,0x3000,0x78800,0x18000,0x60000,0x78800,0x18,0x82218,0x0,0x80000,0x0,0x0,0x0,0x82218,0x0,0x80000,0x0,0x82218,0x0,0x0,};
+   }
+  final private JJCalls[] jj_2_rtns = new JJCalls[3];
+  private boolean jj_rescan = false;
+  private int jj_gc = 0;
+
+  /** Constructor with InputStream. */
+  public ELParser(java.io.InputStream stream) {
+     this(stream, null);
+  }
+  /** Constructor with InputStream and supplied encoding */
+  public ELParser(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source = new ELParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 36; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream stream) {
+     ReInit(stream, null);
+  }
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 36; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  /** Constructor. */
+  public ELParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ELParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 36; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 36; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  /** Constructor with generated Token Manager. */
+  public ELParser(ELParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 36; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  /** Reinitialise. */
+  public void ReInit(ELParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 36; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      if (++jj_gc > 100) {
+        jj_gc = 0;
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+          JJCalls c = jj_2_rtns[i];
+          while (c != null) {
+            if (c.gen < jj_gen) c.first = null;
+            c = c.next;
+          }
+        }
+      }
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  static private final class LookaheadSuccess extends java.lang.Error { }
+  final private LookaheadSuccess jj_ls = new LookaheadSuccess();
+  private boolean jj_scan_token(int kind) {
+    if (jj_scanpos == jj_lastpos) {
+      jj_la--;
+      if (jj_scanpos.next == null) {
+        jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+      } else {
+        jj_lastpos = jj_scanpos = jj_scanpos.next;
+      }
+    } else {
+      jj_scanpos = jj_scanpos.next;
+    }
+    if (jj_rescan) {
+      int i = 0; Token tok = token;
+      while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+      if (tok != null) jj_add_error_token(kind, i);
+    }
+    if (jj_scanpos.kind != kind) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls;
+    return false;
+  }
+
+
+/** Get the next Token. */
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+/** Get the specific Token. */
+  final public Token getToken(int index) {
+    Token t = token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.List<int[]> jj_expentries = new java.util.ArrayList<int[]>();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+  private int[] jj_lasttokens = new int[100];
+  private int jj_endpos;
+
+  private void jj_add_error_token(int kind, int pos) {
+    if (pos >= 100) return;
+    if (pos == jj_endpos + 1) {
+      jj_lasttokens[jj_endpos++] = kind;
+    } else if (jj_endpos != 0) {
+      jj_expentry = new int[jj_endpos];
+      for (int i = 0; i < jj_endpos; i++) {
+        jj_expentry[i] = jj_lasttokens[i];
+      }
+      jj_entries_loop: for (java.util.Iterator<?> it = jj_expentries.iterator(); it.hasNext();) {
+        int[] oldentry = (int[])(it.next());
+        if (oldentry.length == jj_expentry.length) {
+          for (int i = 0; i < jj_expentry.length; i++) {
+            if (oldentry[i] != jj_expentry[i]) {
+              continue jj_entries_loop;
+            }
+          }
+          jj_expentries.add(jj_expentry);
+          break jj_entries_loop;
+        }
+      }
+      if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+    }
+  }
+
+  /** Generate ParseException. */
+  public ParseException generateParseException() {
+    jj_expentries.clear();
+    boolean[] la1tokens = new boolean[57];
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 36; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+          if ((jj_la1_1[i] & (1<<j)) != 0) {
+            la1tokens[32+j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 57; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.add(jj_expentry);
+      }
+    }
+    jj_endpos = 0;
+    jj_rescan_token();
+    jj_add_error_token(0, 0);
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = jj_expentries.get(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  /** Enable tracing. */
+  final public void enable_tracing() {
+  }
+
+  /** Disable tracing. */
+  final public void disable_tracing() {
+  }
+
+  private void jj_rescan_token() {
+    jj_rescan = true;
+    for (int i = 0; i < 3; i++) {
+    try {
+      JJCalls p = jj_2_rtns[i];
+      do {
+        if (p.gen > jj_gen) {
+          jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+          switch (i) {
+            case 0: jj_3_1(); break;
+            case 1: jj_3_2(); break;
+            case 2: jj_3_3(); break;
+          }
+        }
+        p = p.next;
+      } while (p != null);
+      } catch(LookaheadSuccess ls) { }
+    }
+    jj_rescan = false;
+  }
+
+  private void jj_save(int index, int xla) {
+    JJCalls p = jj_2_rtns[index];
+    while (p.gen > jj_gen) {
+      if (p.next == null) { p = p.next = new JJCalls(); break; }
+      p = p.next;
+    }
+    p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+  }
+
+  static final class JJCalls {
+    int gen;
+    Token first;
+    int arg;
+    JJCalls next;
+  }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.jjt b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.jjt
new file mode 100644
index 0000000..f3b6d45
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParser.jjt
@@ -0,0 +1,477 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+    Author:    Jacob Hookom
+    Email:    jacob at hookom.net
+*/
+
+/* == Option Declaration == */
+options
+{
+    STATIC=false;
+    NODE_PREFIX="Ast";
+    VISITOR_EXCEPTION="javax.el.ELException";
+    VISITOR=false;
+    MULTI=true;
+    NODE_DEFAULT_VOID=true;
+    JAVA_UNICODE_ESCAPE=false;
+    UNICODE_INPUT=true;
+    BUILD_NODE_FILES=true;
+}
+
+/* == Parser Declaration == */
+PARSER_BEGIN( ELParser )
+package org.apache.el.parser;
+import java.io.StringReader;
+import javax.el.ELException;
+public class ELParser
+{
+    public static Node parse(String ref) throws ELException
+    {
+        try {
+            return (new ELParser(new StringReader(ref))).CompositeExpression();
+        } catch (ParseException pe) {
+            throw new ELException(pe.getMessage());
+        }
+    }
+}
+PARSER_END( ELParser )
+
+/*
+ * CompositeExpression
+ * Allow most flexible parsing, restrict by examining
+ * type of returned node
+ */
+AstCompositeExpression CompositeExpression() #CompositeExpression : {}
+{
+    (DeferredExpression() | DynamicExpression() | LiteralExpression())* <EOF> { return jjtThis; }
+}
+
+/*
+ * LiteralExpression
+ * Non-EL Expression blocks
+ */
+void LiteralExpression() #LiteralExpression : { Token t = null; }
+{
+    t=<LITERAL_EXPRESSION> { jjtThis.setImage(t.image); }
+}
+
+/*
+ * DeferredExpression
+ * #{..} Expressions
+ */
+void DeferredExpression() #DeferredExpression : {}
+{
+    <START_DEFERRED_EXPRESSION> Expression() <END_EXPRESSION>
+}
+
+/*
+ * DynamicExpression
+ * ${..} Expressions
+ */
+void DynamicExpression() #DynamicExpression : {}
+{
+    <START_DYNAMIC_EXPRESSION> Expression() <END_EXPRESSION>
+}
+
+/*
+ * Expression
+ * EL Expression Language Root, goes to Choice
+ */
+void Expression() : {}
+{
+    Choice()
+}
+
+/*
+ * Choice
+ * For Choice markup a ? b : c, then Or
+ */
+void Choice() : {}
+{
+    Or() (LOOKAHEAD(3) <QUESTIONMARK> Choice() <COLON> Choice() #Choice(3))*
+}
+
+/*
+ * Or
+ * For 'or' '||', then And
+ */
+void Or() : {}
+{
+    And() ((<OR0>|<OR1>) And() #Or(2))*
+}
+
+/*
+ * And
+ * For 'and' '&&', then Equality
+ */
+void And() : {}
+{
+    Equality() ((<AND0>|<AND1>) Equality() #And(2))*
+}
+
+/*
+ * Equality
+ * For '==' 'eq' '!=' 'ne', then Compare
+ */
+void Equality() : {}
+{
+    Compare()
+    (
+        ((<EQ0>|<EQ1>) Compare() #Equal(2))
+    |
+        ((<NE0>|<NE1>) Compare() #NotEqual(2))
+    )*
+}
+
+/*
+ * Compare
+ * For a bunch of them, then Math
+ */
+void Compare() : {}
+{
+    Math()
+    (
+        ((<LT0>|<LT1>) Math() #LessThan(2))
+    |
+        ((<GT0>|<GT1>) Math() #GreaterThan(2))
+    |
+        ((<LE0>|<LE1>) Math() #LessThanEqual(2))
+    |
+        ((<GE0>|<GE1>) Math() #GreaterThanEqual(2))
+    )*
+}
+
+/*
+ * Math
+ * For '+' '-', then Multiplication
+ */
+void Math() : {}
+{
+    Multiplication()
+    (
+        (<PLUS> Multiplication() #Plus(2))
+    |
+        (<MINUS> Multiplication() #Minus(2))
+    )*
+}
+
+/*
+ * Multiplication
+ * For a bunch of them, then Unary
+ */
+void Multiplication() : {}
+{
+    Unary()
+    (
+        (<MULT> Unary() #Mult(2))
+    |
+        ((<DIV0>|<DIV1>) Unary() #Div(2))
+    |
+        ((<MOD0>|<MOD1>) Unary() #Mod(2))
+    )*
+}
+
+/*
+ * Unary
+ * For '-' '!' 'not' 'empty', then Value
+ */
+void Unary() : {}
+{
+        <MINUS> Unary() #Negative
+    |
+        (<NOT0>|<NOT1>) Unary() #Not
+    |
+        <EMPTY> Unary() #Empty
+    |
+        Value()
+}
+
+/*
+ * Value
+ * Defines Prefix plus zero or more Suffixes
+ */
+void Value() : {}
+{
+    (ValuePrefix() (ValueSuffix())*) #Value(>1)
+}
+
+/*
+ * ValuePrefix
+ * For Literals, Variables, and Functions
+ */
+void ValuePrefix() : {}
+{
+    Literal()
+    | NonLiteral()
+}
+
+/*
+ * ValueSuffix
+ * Either dot or bracket notation
+ */
+void ValueSuffix() : {}
+{
+    ( DotSuffix() | BracketSuffix() ) ( MethodParameters())?
+}
+
+/*
+ * DotSuffix
+ * Dot Property
+ */
+void DotSuffix() #DotSuffix : { Token t = null; }
+{
+    <DOT> t=<IDENTIFIER> { jjtThis.setImage(t.image); }
+}
+
+/*
+ * BracketSuffix
+ * Sub Expression Suffix
+ */
+void BracketSuffix() #BracketSuffix : {}
+{
+    <LBRACK> Expression() <RBRACK>
+}
+
+/*
+ * MethodParameters
+ */
+void MethodParameters() #MethodParameters : {}
+{
+    <LPAREN> ( Expression() ( <COMMA> Expression())* )? <RPAREN>
+}
+
+/*
+ * NonLiteral
+ * For Grouped Operations, Identifiers, and Functions
+ */
+void NonLiteral() : {}
+{
+    <LPAREN> Expression() <RPAREN>
+    | LOOKAHEAD((<IDENTIFIER> <COLON>)? <IDENTIFIER> <LPAREN>) Function()
+    | Identifier()
+}
+
+/*
+ * Identifier
+ * Java Language Identifier
+ */
+void Identifier() #Identifier : { Token t = null; }
+{
+    t=<IDENTIFIER> { jjtThis.setImage(t.image); }
+}
+
+/*
+ * Function
+ * Namespace:Name(a,b,c)
+ */
+void Function() #Function :
+{
+    Token t0 = null;
+    Token t1 = null;
+}
+{
+    (LOOKAHEAD(2) t0=<IDENTIFIER> <COLON>)? t1=<IDENTIFIER>
+    {
+        if (t0 != null) {
+            jjtThis.setPrefix(t0.image);
+            jjtThis.setLocalName(t1.image);
+        } else {
+            jjtThis.setLocalName(t1.image);
+        }
+    }
+    <LPAREN> (Expression() (<COMMA> Expression())*)? <RPAREN>
+}
+
+/*
+ * Literal
+ * Reserved Keywords
+ */
+void Literal() : {}
+{
+    Boolean()
+    | FloatingPoint()
+    | Integer()
+    | String()
+    | Null()
+}
+
+/*
+ * Boolean
+ * For 'true' 'false'
+ */
+void Boolean() : {}
+{
+    <TRUE> #True
+    | <FALSE> #False
+}
+
+/*
+ * FloatinPoint
+ * For Decimal and Floating Point Literals
+ */
+void FloatingPoint() #FloatingPoint : { Token t = null; }
+{
+    t=<FLOATING_POINT_LITERAL> { jjtThis.setImage(t.image); }
+}
+
+/*
+ * Integer
+ * For Simple Numeric Literals
+ */
+void Integer() #Integer : { Token t = null; }
+{
+    t=<INTEGER_LITERAL> { jjtThis.setImage(t.image); }
+}
+
+/*
+ * String
+ * For Quoted Literals
+ */
+void String() #String : { Token t = null; }
+{
+    t=<STRING_LITERAL> { jjtThis.setImage(t.image); }
+}
+
+/*
+ * Null
+ * For 'null'
+ */
+void Null() #Null : {}
+{
+    <NULL>
+}
+
+
+/* ==================================================================================== */
+<DEFAULT> TOKEN :
+{
+  /*
+   * The following definition uses + rather than * in two places to prevent
+   * LITERAL_EXPRESSION matching the empty string that could result in the
+   * Parser entering an infinite loop.
+   */
+  < LITERAL_EXPRESSION:
+    (   (~["$", "#", "\\"])* "\\" (["$", "#"])?
+      | (~["$", "#"])* (["$", "#"] ~["{", "$", "#"])
+      | (~["$", "#"])+
+    )+
+    | "$"
+    | "#"
+  >
+|
+  < START_DYNAMIC_EXPRESSION: "${" > : IN_EXPRESSION
+|
+  < START_DEFERRED_EXPRESSION: "#{" > : IN_EXPRESSION
+}
+
+<IN_EXPRESSION> SKIP : { " " | "\t" | "\n" | "\r" }
+
+<IN_EXPRESSION> TOKEN :
+{
+    < INTEGER_LITERAL: ["0"-"9"] (["0"-"9"])* >
+|    < FLOATING_POINT_LITERAL: (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)?
+        | "." (["0"-"9"])+ (<EXPONENT>)?
+        | (["0"-"9"])+ <EXPONENT>
+    >
+|    < #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >
+|    < STRING_LITERAL: ("\"" ((~["\"","\\"])
+        | ("\\" ( ["\\","\"","\'"] )))* "\"")
+        | ("\'" ((~["\'","\\"])
+        | ("\\" ( ["\\","\"","\'"] )))* "\'")
+    >
+|    < TRUE : "true" >
+|    < FALSE : "false" >
+|    < NULL : "null" >
+|    < END_EXPRESSION : "}" > : DEFAULT
+|    < DOT : "." >
+|    < LPAREN : "(" >
+|    < RPAREN : ")" >
+|    < LBRACK : "[" >
+|    < RBRACK : "]" >
+|    < COLON : ":" >
+|    < COMMA : "," >
+|    < GT0 : ">" >
+|    < GT1 : "gt" >
+|    < LT0 : "<" >
+|    < LT1 : "lt" >
+|    < GE0 : ">=" >
+|    < GE1 : "ge" >
+|    < LE0 : "<=" >
+|    < LE1 : "le" >
+|    < EQ0 : "==" >
+|    < EQ1 : "eq" >
+|    < NE0 : "!=" >
+|    < NE1 : "ne" >
+|    < NOT0 : "!" >
+|    < NOT1 : "not" >
+|    < AND0 : "&&" >
+|    < AND1 : "and" >
+|    < OR0 : "||" >
+|    < OR1 : "or" >
+|    < EMPTY : "empty" >
+|    < INSTANCEOF : "instanceof" >
+|    < MULT : "*" >
+|    < PLUS : "+" >
+|    < MINUS : "-" >
+|    < QUESTIONMARK : "?" >
+|    < DIV0 : "/" >
+|    < DIV1 : "div" >
+|    < MOD0 : "%" >
+|    < MOD1 : "mod" >
+|    < IDENTIFIER : (<LETTER>|<IMPL_OBJ_START>) (<LETTER>|<DIGIT>)* >
+|    < FUNCTIONSUFFIX : (<IDENTIFIER>) >
+|    < #IMPL_OBJ_START: "#" >
+|    < #LETTER:
+        [
+        "\u0024",
+        "\u0041"-"\u005a",
+        "\u005f",
+        "\u0061"-"\u007a",
+        "\u00c0"-"\u00d6",
+        "\u00d8"-"\u00f6",
+        "\u00f8"-"\u00ff",
+        "\u0100"-"\u1fff",
+        "\u3040"-"\u318f",
+        "\u3300"-"\u337f",
+        "\u3400"-"\u3d2d",
+        "\u4e00"-"\u9fff",
+        "\uf900"-"\ufaff"
+        ]
+    >
+|    < #DIGIT:
+        [
+        "\u0030"-"\u0039",
+        "\u0660"-"\u0669",
+        "\u06f0"-"\u06f9",
+        "\u0966"-"\u096f",
+        "\u09e6"-"\u09ef",
+        "\u0a66"-"\u0a6f",
+        "\u0ae6"-"\u0aef",
+        "\u0b66"-"\u0b6f",
+        "\u0be7"-"\u0bef",
+        "\u0c66"-"\u0c6f",
+        "\u0ce6"-"\u0cef",
+        "\u0d66"-"\u0d6f",
+        "\u0e50"-"\u0e59",
+        "\u0ed0"-"\u0ed9",
+        "\u1040"-"\u1049"
+        ]
+    >
+|    < ILLEGAL_CHARACTER: (~[]) >
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserConstants.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserConstants.java
new file mode 100644
index 0000000..50c825d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserConstants.java
@@ -0,0 +1,184 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. ELParserConstants.java */
+package org.apache.el.parser;
+
+
+/**
+ * Token literal values and constants.
+ * Generated by org.javacc.parser.OtherFilesGen#start()
+ */
+public interface ELParserConstants {
+
+  /** End of File. */
+  int EOF = 0;
+  /** RegularExpression Id. */
+  int LITERAL_EXPRESSION = 1;
+  /** RegularExpression Id. */
+  int START_DYNAMIC_EXPRESSION = 2;
+  /** RegularExpression Id. */
+  int START_DEFERRED_EXPRESSION = 3;
+  /** RegularExpression Id. */
+  int INTEGER_LITERAL = 8;
+  /** RegularExpression Id. */
+  int FLOATING_POINT_LITERAL = 9;
+  /** RegularExpression Id. */
+  int EXPONENT = 10;
+  /** RegularExpression Id. */
+  int STRING_LITERAL = 11;
+  /** RegularExpression Id. */
+  int TRUE = 12;
+  /** RegularExpression Id. */
+  int FALSE = 13;
+  /** RegularExpression Id. */
+  int NULL = 14;
+  /** RegularExpression Id. */
+  int END_EXPRESSION = 15;
+  /** RegularExpression Id. */
+  int DOT = 16;
+  /** RegularExpression Id. */
+  int LPAREN = 17;
+  /** RegularExpression Id. */
+  int RPAREN = 18;
+  /** RegularExpression Id. */
+  int LBRACK = 19;
+  /** RegularExpression Id. */
+  int RBRACK = 20;
+  /** RegularExpression Id. */
+  int COLON = 21;
+  /** RegularExpression Id. */
+  int COMMA = 22;
+  /** RegularExpression Id. */
+  int GT0 = 23;
+  /** RegularExpression Id. */
+  int GT1 = 24;
+  /** RegularExpression Id. */
+  int LT0 = 25;
+  /** RegularExpression Id. */
+  int LT1 = 26;
+  /** RegularExpression Id. */
+  int GE0 = 27;
+  /** RegularExpression Id. */
+  int GE1 = 28;
+  /** RegularExpression Id. */
+  int LE0 = 29;
+  /** RegularExpression Id. */
+  int LE1 = 30;
+  /** RegularExpression Id. */
+  int EQ0 = 31;
+  /** RegularExpression Id. */
+  int EQ1 = 32;
+  /** RegularExpression Id. */
+  int NE0 = 33;
+  /** RegularExpression Id. */
+  int NE1 = 34;
+  /** RegularExpression Id. */
+  int NOT0 = 35;
+  /** RegularExpression Id. */
+  int NOT1 = 36;
+  /** RegularExpression Id. */
+  int AND0 = 37;
+  /** RegularExpression Id. */
+  int AND1 = 38;
+  /** RegularExpression Id. */
+  int OR0 = 39;
+  /** RegularExpression Id. */
+  int OR1 = 40;
+  /** RegularExpression Id. */
+  int EMPTY = 41;
+  /** RegularExpression Id. */
+  int INSTANCEOF = 42;
+  /** RegularExpression Id. */
+  int MULT = 43;
+  /** RegularExpression Id. */
+  int PLUS = 44;
+  /** RegularExpression Id. */
+  int MINUS = 45;
+  /** RegularExpression Id. */
+  int QUESTIONMARK = 46;
+  /** RegularExpression Id. */
+  int DIV0 = 47;
+  /** RegularExpression Id. */
+  int DIV1 = 48;
+  /** RegularExpression Id. */
+  int MOD0 = 49;
+  /** RegularExpression Id. */
+  int MOD1 = 50;
+  /** RegularExpression Id. */
+  int IDENTIFIER = 51;
+  /** RegularExpression Id. */
+  int FUNCTIONSUFFIX = 52;
+  /** RegularExpression Id. */
+  int IMPL_OBJ_START = 53;
+  /** RegularExpression Id. */
+  int LETTER = 54;
+  /** RegularExpression Id. */
+  int DIGIT = 55;
+  /** RegularExpression Id. */
+  int ILLEGAL_CHARACTER = 56;
+
+  /** Lexical state. */
+  int DEFAULT = 0;
+  /** Lexical state. */
+  int IN_EXPRESSION = 1;
+
+  /** Literal token values. */
+  String[] tokenImage = {
+    "<EOF>",
+    "<LITERAL_EXPRESSION>",
+    "\"${\"",
+    "\"#{\"",
+    "\" \"",
+    "\"\\t\"",
+    "\"\\n\"",
+    "\"\\r\"",
+    "<INTEGER_LITERAL>",
+    "<FLOATING_POINT_LITERAL>",
+    "<EXPONENT>",
+    "<STRING_LITERAL>",
+    "\"true\"",
+    "\"false\"",
+    "\"null\"",
+    "\"}\"",
+    "\".\"",
+    "\"(\"",
+    "\")\"",
+    "\"[\"",
+    "\"]\"",
+    "\":\"",
+    "\",\"",
+    "\">\"",
+    "\"gt\"",
+    "\"<\"",
+    "\"lt\"",
+    "\">=\"",
+    "\"ge\"",
+    "\"<=\"",
+    "\"le\"",
+    "\"==\"",
+    "\"eq\"",
+    "\"!=\"",
+    "\"ne\"",
+    "\"!\"",
+    "\"not\"",
+    "\"&&\"",
+    "\"and\"",
+    "\"||\"",
+    "\"or\"",
+    "\"empty\"",
+    "\"instanceof\"",
+    "\"*\"",
+    "\"+\"",
+    "\"-\"",
+    "\"?\"",
+    "\"/\"",
+    "\"div\"",
+    "\"%\"",
+    "\"mod\"",
+    "<IDENTIFIER>",
+    "<FUNCTIONSUFFIX>",
+    "\"#\"",
+    "<LETTER>",
+    "<DIGIT>",
+    "<ILLEGAL_CHARACTER>",
+  };
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserTokenManager.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserTokenManager.java
new file mode 100644
index 0000000..92fdae8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserTokenManager.java
@@ -0,0 +1,1292 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. ELParserTokenManager.java */
+package org.apache.el.parser;
+
+/** Token Manager. */
+public class ELParserTokenManager implements ELParserConstants
+{
+
+  /** Debug output. */
+  public  java.io.PrintStream debugStream = System.out;
+  /** Set debug output. */
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      case 0:
+         if ((active0 & 0xcL) != 0L)
+         {
+            jjmatchedKind = 1;
+            return 5;
+         }
+         return -1;
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 35:
+         return jjMoveStringLiteralDfa1_0(0x8L);
+      case 36:
+         return jjMoveStringLiteralDfa1_0(0x4L);
+      default :
+         return jjMoveNfa_0(7, 0);
+   }
+}
+private int jjMoveStringLiteralDfa1_0(long active0)
+{
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(0, active0);
+      return 1;
+   }
+   switch(curChar)
+   {
+      case 123:
+         if ((active0 & 0x4L) != 0L)
+            return jjStopAtPos(1, 2);
+         else if ((active0 & 0x8L) != 0L)
+            return jjStopAtPos(1, 3);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_0(0, active0);
+}
+static final long[] jjbitVec0 = {
+   0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec2 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private int jjMoveNfa_0(int startState, int curPos)
+{
+   int startsAt = 0;
+   jjnewStateCnt = 8;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 7:
+                  if ((0xffffffe7ffffffffL & l) != 0L)
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                     jjCheckNAddStates(0, 4);
+                  }
+                  else if ((0x1800000000L & l) != 0L)
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                     jjCheckNAdd(5);
+                  }
+                  if ((0xffffffe7ffffffffL & l) != 0L)
+                     jjCheckNAddTwoStates(0, 1);
+                  break;
+               case 0:
+                  if ((0xffffffe7ffffffffL & l) != 0L)
+                     jjCheckNAddTwoStates(0, 1);
+                  break;
+               case 2:
+                  if ((0xffffffe7ffffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 4);
+                  break;
+               case 3:
+                  if ((0xffffffe7ffffffffL & l) != 0L)
+                     jjCheckNAddTwoStates(3, 4);
+                  break;
+               case 4:
+                  if ((0x1800000000L & l) != 0L)
+                     jjCheckNAdd(5);
+                  break;
+               case 5:
+                  if ((0xffffffe7ffffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(5, 8);
+                  break;
+               case 6:
+                  if ((0x1800000000L & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(9, 13);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 7:
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 4);
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddTwoStates(0, 1);
+                  else if (curChar == 92)
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                     jjCheckNAddStates(14, 17);
+                  }
+                  break;
+               case 0:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddTwoStates(0, 1);
+                  break;
+               case 1:
+                  if (curChar != 92)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(14, 17);
+                  break;
+               case 2:
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 4);
+                  break;
+               case 3:
+                  jjCheckNAddTwoStates(3, 4);
+                  break;
+               case 5:
+                  if ((0xf7ffffffffffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(5, 8);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int hiByte = (int)(curChar >> 8);
+         int i1 = hiByte >> 6;
+         long l1 = 1L << (hiByte & 077);
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 7:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddTwoStates(0, 1);
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                     jjCheckNAddStates(0, 4);
+                  }
+                  break;
+               case 0:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddTwoStates(0, 1);
+                  break;
+               case 2:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 4);
+                  break;
+               case 3:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjCheckNAddTwoStates(3, 4);
+                  break;
+               case 5:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(5, 8);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 8 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_1(int pos, long active0)
+{
+   switch (pos)
+   {
+      case 0:
+         if ((active0 & 0x10000L) != 0L)
+            return 1;
+         if ((active0 & 0x5075555007000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            return 30;
+         }
+         return -1;
+      case 1:
+         if ((active0 & 0x5065000007000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 1;
+            return 30;
+         }
+         if ((active0 & 0x10555000000L) != 0L)
+            return 30;
+         return -1;
+      case 2:
+         if ((active0 & 0x5005000000000L) != 0L)
+            return 30;
+         if ((active0 & 0x60000007000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 2;
+            return 30;
+         }
+         return -1;
+      case 3:
+         if ((active0 & 0x5000L) != 0L)
+            return 30;
+         if ((active0 & 0x60000002000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 3;
+            return 30;
+         }
+         return -1;
+      case 4:
+         if ((active0 & 0x40000000000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 4;
+            return 30;
+         }
+         if ((active0 & 0x20000002000L) != 0L)
+            return 30;
+         return -1;
+      case 5:
+         if ((active0 & 0x40000000000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 5;
+            return 30;
+         }
+         return -1;
+      case 6:
+         if ((active0 & 0x40000000000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 6;
+            return 30;
+         }
+         return -1;
+      case 7:
+         if ((active0 & 0x40000000000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 7;
+            return 30;
+         }
+         return -1;
+      case 8:
+         if ((active0 & 0x40000000000L) != 0L)
+         {
+            jjmatchedKind = 51;
+            jjmatchedPos = 8;
+            return 30;
+         }
+         return -1;
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_1(int pos, long active0)
+{
+   return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_1()
+{
+   switch(curChar)
+   {
+      case 33:
+         jjmatchedKind = 35;
+         return jjMoveStringLiteralDfa1_1(0x200000000L);
+      case 37:
+         return jjStopAtPos(0, 49);
+      case 38:
+         return jjMoveStringLiteralDfa1_1(0x2000000000L);
+      case 40:
+         return jjStopAtPos(0, 17);
+      case 41:
+         return jjStopAtPos(0, 18);
+      case 42:
+         return jjStopAtPos(0, 43);
+      case 43:
+         return jjStopAtPos(0, 44);
+      case 44:
+         return jjStopAtPos(0, 22);
+      case 45:
+         return jjStopAtPos(0, 45);
+      case 46:
+         return jjStartNfaWithStates_1(0, 16, 1);
+      case 47:
+         return jjStopAtPos(0, 47);
+      case 58:
+         return jjStopAtPos(0, 21);
+      case 60:
+         jjmatchedKind = 25;
+         return jjMoveStringLiteralDfa1_1(0x20000000L);
+      case 61:
+         return jjMoveStringLiteralDfa1_1(0x80000000L);
+      case 62:
+         jjmatchedKind = 23;
+         return jjMoveStringLiteralDfa1_1(0x8000000L);
+      case 63:
+         return jjStopAtPos(0, 46);
+      case 91:
+         return jjStopAtPos(0, 19);
+      case 93:
+         return jjStopAtPos(0, 20);
+      case 97:
+         return jjMoveStringLiteralDfa1_1(0x4000000000L);
+      case 100:
+         return jjMoveStringLiteralDfa1_1(0x1000000000000L);
+      case 101:
+         return jjMoveStringLiteralDfa1_1(0x20100000000L);
+      case 102:
+         return jjMoveStringLiteralDfa1_1(0x2000L);
+      case 103:
+         return jjMoveStringLiteralDfa1_1(0x11000000L);
+      case 105:
+         return jjMoveStringLiteralDfa1_1(0x40000000000L);
+      case 108:
+         return jjMoveStringLiteralDfa1_1(0x44000000L);
+      case 109:
+         return jjMoveStringLiteralDfa1_1(0x4000000000000L);
+      case 110:
+         return jjMoveStringLiteralDfa1_1(0x1400004000L);
+      case 111:
+         return jjMoveStringLiteralDfa1_1(0x10000000000L);
+      case 116:
+         return jjMoveStringLiteralDfa1_1(0x1000L);
+      case 124:
+         return jjMoveStringLiteralDfa1_1(0x8000000000L);
+      case 125:
+         return jjStopAtPos(0, 15);
+      default :
+         return jjMoveNfa_1(0, 0);
+   }
+}
+private int jjMoveStringLiteralDfa1_1(long active0)
+{
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(0, active0);
+      return 1;
+   }
+   switch(curChar)
+   {
+      case 38:
+         if ((active0 & 0x2000000000L) != 0L)
+            return jjStopAtPos(1, 37);
+         break;
+      case 61:
+         if ((active0 & 0x8000000L) != 0L)
+            return jjStopAtPos(1, 27);
+         else if ((active0 & 0x20000000L) != 0L)
+            return jjStopAtPos(1, 29);
+         else if ((active0 & 0x80000000L) != 0L)
+            return jjStopAtPos(1, 31);
+         else if ((active0 & 0x200000000L) != 0L)
+            return jjStopAtPos(1, 33);
+         break;
+      case 97:
+         return jjMoveStringLiteralDfa2_1(active0, 0x2000L);
+      case 101:
+         if ((active0 & 0x10000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 28, 30);
+         else if ((active0 & 0x40000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 30, 30);
+         else if ((active0 & 0x400000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 34, 30);
+         break;
+      case 105:
+         return jjMoveStringLiteralDfa2_1(active0, 0x1000000000000L);
+      case 109:
+         return jjMoveStringLiteralDfa2_1(active0, 0x20000000000L);
+      case 110:
+         return jjMoveStringLiteralDfa2_1(active0, 0x44000000000L);
+      case 111:
+         return jjMoveStringLiteralDfa2_1(active0, 0x4001000000000L);
+      case 113:
+         if ((active0 & 0x100000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 32, 30);
+         break;
+      case 114:
+         if ((active0 & 0x10000000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 40, 30);
+         return jjMoveStringLiteralDfa2_1(active0, 0x1000L);
+      case 116:
+         if ((active0 & 0x1000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 24, 30);
+         else if ((active0 & 0x4000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 26, 30);
+         break;
+      case 117:
+         return jjMoveStringLiteralDfa2_1(active0, 0x4000L);
+      case 124:
+         if ((active0 & 0x8000000000L) != 0L)
+            return jjStopAtPos(1, 39);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(0, active0);
+}
+private int jjMoveStringLiteralDfa2_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(0, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(1, active0);
+      return 2;
+   }
+   switch(curChar)
+   {
+      case 100:
+         if ((active0 & 0x4000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 38, 30);
+         else if ((active0 & 0x4000000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 50, 30);
+         break;
+      case 108:
+         return jjMoveStringLiteralDfa3_1(active0, 0x6000L);
+      case 112:
+         return jjMoveStringLiteralDfa3_1(active0, 0x20000000000L);
+      case 115:
+         return jjMoveStringLiteralDfa3_1(active0, 0x40000000000L);
+      case 116:
+         if ((active0 & 0x1000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 36, 30);
+         break;
+      case 117:
+         return jjMoveStringLiteralDfa3_1(active0, 0x1000L);
+      case 118:
+         if ((active0 & 0x1000000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 48, 30);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(1, active0);
+}
+private int jjMoveStringLiteralDfa3_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(1, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(2, active0);
+      return 3;
+   }
+   switch(curChar)
+   {
+      case 101:
+         if ((active0 & 0x1000L) != 0L)
+            return jjStartNfaWithStates_1(3, 12, 30);
+         break;
+      case 108:
+         if ((active0 & 0x4000L) != 0L)
+            return jjStartNfaWithStates_1(3, 14, 30);
+         break;
+      case 115:
+         return jjMoveStringLiteralDfa4_1(active0, 0x2000L);
+      case 116:
+         return jjMoveStringLiteralDfa4_1(active0, 0x60000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(2, active0);
+}
+private int jjMoveStringLiteralDfa4_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(2, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(3, active0);
+      return 4;
+   }
+   switch(curChar)
+   {
+      case 97:
+         return jjMoveStringLiteralDfa5_1(active0, 0x40000000000L);
+      case 101:
+         if ((active0 & 0x2000L) != 0L)
+            return jjStartNfaWithStates_1(4, 13, 30);
+         break;
+      case 121:
+         if ((active0 & 0x20000000000L) != 0L)
+            return jjStartNfaWithStates_1(4, 41, 30);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(3, active0);
+}
+private int jjMoveStringLiteralDfa5_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(3, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(4, active0);
+      return 5;
+   }
+   switch(curChar)
+   {
+      case 110:
+         return jjMoveStringLiteralDfa6_1(active0, 0x40000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(4, active0);
+}
+private int jjMoveStringLiteralDfa6_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(4, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(5, active0);
+      return 6;
+   }
+   switch(curChar)
+   {
+      case 99:
+         return jjMoveStringLiteralDfa7_1(active0, 0x40000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(5, active0);
+}
+private int jjMoveStringLiteralDfa7_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(5, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(6, active0);
+      return 7;
+   }
+   switch(curChar)
+   {
+      case 101:
+         return jjMoveStringLiteralDfa8_1(active0, 0x40000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(6, active0);
+}
+private int jjMoveStringLiteralDfa8_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(6, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(7, active0);
+      return 8;
+   }
+   switch(curChar)
+   {
+      case 111:
+         return jjMoveStringLiteralDfa9_1(active0, 0x40000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(7, active0);
+}
+private int jjMoveStringLiteralDfa9_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(7, old0);
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(8, active0);
+      return 9;
+   }
+   switch(curChar)
+   {
+      case 102:
+         if ((active0 & 0x40000000000L) != 0L)
+            return jjStartNfaWithStates_1(9, 42, 30);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(8, active0);
+}
+private int jjStartNfaWithStates_1(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_1(state, pos + 1);
+}
+static final long[] jjbitVec3 = {
+   0x1ff00000fffffffeL, 0xffffffffffffc000L, 0xffffffffL, 0x600000000000000L
+};
+static final long[] jjbitVec4 = {
+   0x0L, 0x0L, 0x0L, 0xff7fffffff7fffffL
+};
+static final long[] jjbitVec5 = {
+   0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec6 = {
+   0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffL, 0x0L
+};
+static final long[] jjbitVec7 = {
+   0xffffffffffffffffL, 0xffffffffffffffffL, 0x0L, 0x0L
+};
+static final long[] jjbitVec8 = {
+   0x3fffffffffffL, 0x0L, 0x0L, 0x0L
+};
+private int jjMoveNfa_1(int startState, int curPos)
+{
+   int startsAt = 0;
+   jjnewStateCnt = 30;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 8)
+                        kind = 8;
+                     jjCheckNAddStates(18, 22);
+                  }
+                  else if ((0x1800000000L & l) != 0L)
+                  {
+                     if (kind > 51)
+                        kind = 51;
+                     jjCheckNAddTwoStates(28, 29);
+                  }
+                  else if (curChar == 39)
+                     jjCheckNAddStates(23, 25);
+                  else if (curChar == 34)
+                     jjCheckNAddStates(26, 28);
+                  else if (curChar == 46)
+                     jjCheckNAdd(1);
+                  break;
+               case 30:
+                  if ((0x3ff001000000000L & l) != 0L)
+                  {
+                     if (kind > 52)
+                        kind = 52;
+                     jjCheckNAdd(29);
+                  }
+                  if ((0x3ff001000000000L & l) != 0L)
+                  {
+                     if (kind > 51)
+                        kind = 51;
+                     jjCheckNAdd(28);
+                  }
+                  break;
+               case 1:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAddTwoStates(1, 2);
+                  break;
+               case 3:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(4);
+                  break;
+               case 4:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAdd(4);
+                  break;
+               case 5:
+                  if (curChar == 34)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 6:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 8:
+                  if ((0x8400000000L & l) != 0L)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 9:
+                  if (curChar == 34 && kind > 11)
+                     kind = 11;
+                  break;
+               case 10:
+                  if (curChar == 39)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 11:
+                  if ((0xffffff7fffffffffL & l) != 0L)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 13:
+                  if ((0x8400000000L & l) != 0L)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 14:
+                  if (curChar == 39 && kind > 11)
+                     kind = 11;
+                  break;
+               case 15:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 8)
+                     kind = 8;
+                  jjCheckNAddStates(18, 22);
+                  break;
+               case 16:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 8)
+                     kind = 8;
+                  jjCheckNAdd(16);
+                  break;
+               case 17:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(17, 18);
+                  break;
+               case 18:
+                  if (curChar != 46)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAddTwoStates(19, 20);
+                  break;
+               case 19:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAddTwoStates(19, 20);
+                  break;
+               case 21:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(22);
+                  break;
+               case 22:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAdd(22);
+                  break;
+               case 23:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(23, 24);
+                  break;
+               case 25:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(26);
+                  break;
+               case 26:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAdd(26);
+                  break;
+               case 27:
+                  if ((0x1800000000L & l) == 0L)
+                     break;
+                  if (kind > 51)
+                     kind = 51;
+                  jjCheckNAddTwoStates(28, 29);
+                  break;
+               case 28:
+                  if ((0x3ff001000000000L & l) == 0L)
+                     break;
+                  if (kind > 51)
+                     kind = 51;
+                  jjCheckNAdd(28);
+                  break;
+               case 29:
+                  if ((0x3ff001000000000L & l) == 0L)
+                     break;
+                  if (kind > 52)
+                     kind = 52;
+                  jjCheckNAdd(29);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 51)
+                     kind = 51;
+                  jjCheckNAddTwoStates(28, 29);
+                  break;
+               case 30:
+                  if ((0x7fffffe87fffffeL & l) != 0L)
+                  {
+                     if (kind > 52)
+                        kind = 52;
+                     jjCheckNAdd(29);
+                  }
+                  if ((0x7fffffe87fffffeL & l) != 0L)
+                  {
+                     if (kind > 51)
+                        kind = 51;
+                     jjCheckNAdd(28);
+                  }
+                  break;
+               case 2:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(29, 30);
+                  break;
+               case 6:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 7:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 8;
+                  break;
+               case 8:
+                  if (curChar == 92)
+                     jjCheckNAddStates(26, 28);
+                  break;
+               case 11:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 12:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 13;
+                  break;
+               case 13:
+                  if (curChar == 92)
+                     jjCheckNAddStates(23, 25);
+                  break;
+               case 20:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(31, 32);
+                  break;
+               case 24:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(33, 34);
+                  break;
+               case 28:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 51)
+                     kind = 51;
+                  jjCheckNAdd(28);
+                  break;
+               case 29:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 52)
+                     kind = 52;
+                  jjCheckNAdd(29);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int hiByte = (int)(curChar >> 8);
+         int i1 = hiByte >> 6;
+         long l1 = 1L << (hiByte & 077);
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 51)
+                     kind = 51;
+                  jjCheckNAddTwoStates(28, 29);
+                  break;
+               case 30:
+                  if (jjCanMove_1(hiByte, i1, i2, l1, l2))
+                  {
+                     if (kind > 51)
+                        kind = 51;
+                     jjCheckNAdd(28);
+                  }
+                  if (jjCanMove_1(hiByte, i1, i2, l1, l2))
+                  {
+                     if (kind > 52)
+                        kind = 52;
+                     jjCheckNAdd(29);
+                  }
+                  break;
+               case 6:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjAddStates(26, 28);
+                  break;
+               case 11:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjAddStates(23, 25);
+                  break;
+               case 28:
+                  if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 51)
+                     kind = 51;
+                  jjCheckNAdd(28);
+                  break;
+               case 29:
+                  if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 52)
+                     kind = 52;
+                  jjCheckNAdd(29);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 30 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   0, 1, 3, 4, 2, 0, 1, 4, 2, 0, 1, 4, 5, 2, 0, 1, 
+   2, 6, 16, 17, 18, 23, 24, 11, 12, 14, 6, 7, 9, 3, 4, 21, 
+   22, 25, 26, 
+};
+private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, long l2)
+{
+   switch(hiByte)
+   {
+      case 0:
+         return ((jjbitVec2[i2] & l2) != 0L);
+      default :
+         if ((jjbitVec0[i1] & l1) != 0L)
+            return true;
+         return false;
+   }
+}
+private static final boolean jjCanMove_1(int hiByte, int i1, int i2, long l1, long l2)
+{
+   switch(hiByte)
+   {
+      case 0:
+         return ((jjbitVec4[i2] & l2) != 0L);
+      case 48:
+         return ((jjbitVec5[i2] & l2) != 0L);
+      case 49:
+         return ((jjbitVec6[i2] & l2) != 0L);
+      case 51:
+         return ((jjbitVec7[i2] & l2) != 0L);
+      case 61:
+         return ((jjbitVec8[i2] & l2) != 0L);
+      default :
+         if ((jjbitVec3[i1] & l1) != 0L)
+            return true;
+         return false;
+   }
+}
+
+/** Token literal values. */
+public static final String[] jjstrLiteralImages = {
+"", null, "\44\173", "\43\173", null, null, null, null, null, null, null, null, 
+"\164\162\165\145", "\146\141\154\163\145", "\156\165\154\154", "\175", "\56", "\50", "\51", 
+"\133", "\135", "\72", "\54", "\76", "\147\164", "\74", "\154\164", "\76\75", 
+"\147\145", "\74\75", "\154\145", "\75\75", "\145\161", "\41\75", "\156\145", "\41", 
+"\156\157\164", "\46\46", "\141\156\144", "\174\174", "\157\162", "\145\155\160\164\171", 
+"\151\156\163\164\141\156\143\145\157\146", "\52", "\53", "\55", "\77", "\57", "\144\151\166", "\45", "\155\157\144", null, 
+null, null, null, null, null, };
+
+/** Lexer state names. */
+public static final String[] lexStateNames = {
+   "DEFAULT",
+   "IN_EXPRESSION",
+};
+
+/** Lex State array. */
+public static final int[] jjnewLexState = {
+   -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   -1, -1, -1, -1, -1, -1, -1, 
+};
+static final long[] jjtoToken = {
+   0x11ffffffffffb0fL, 
+};
+static final long[] jjtoSkip = {
+   0xf0L, 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[30];
+private final int[] jjstateSet = new int[60];
+protected char curChar;
+/** Constructor. */
+public ELParserTokenManager(SimpleCharStream stream){
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+
+/** Constructor. */
+public ELParserTokenManager(SimpleCharStream stream, int lexState){
+   this(stream);
+   SwitchTo(lexState);
+}
+
+/** Reinitialise parser. */
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 30; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+
+/** Reinitialise parser. */
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+
+/** Switch to specified lex state. */
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 2 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+   final Token t;
+   final String curTokenImage;
+   final int beginLine;
+   final int endLine;
+   final int beginColumn;
+   final int endColumn;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   curTokenImage = (im == null) ? input_stream.GetImage() : im;
+   beginLine = input_stream.getBeginLine();
+   beginColumn = input_stream.getBeginColumn();
+   endLine = input_stream.getEndLine();
+   endColumn = input_stream.getEndColumn();
+   t = Token.newToken(jjmatchedKind, curTokenImage);
+
+   t.beginLine = beginLine;
+   t.endLine = endLine;
+   t.beginColumn = beginColumn;
+   t.endColumn = endColumn;
+
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+/** Get the next Token. */
+public Token getNextToken() 
+{
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {
+   try
+   {
+      curChar = input_stream.BeginToken();
+   }
+   catch(java.io.IOException e)
+   {
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+
+   switch(curLexState)
+   {
+     case 0:
+       jjmatchedKind = 0x7fffffff;
+       jjmatchedPos = 0;
+       curPos = jjMoveStringLiteralDfa0_0();
+       break;
+     case 1:
+       try { input_stream.backup(0);
+          while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)
+             curChar = input_stream.BeginToken();
+       }
+       catch (java.io.IOException e1) { continue EOFLoop; }
+       jjmatchedKind = 0x7fffffff;
+       jjmatchedPos = 0;
+       curPos = jjMoveStringLiteralDfa0_1();
+       if (jjmatchedPos == 0 && jjmatchedKind > 56)
+       {
+          jjmatchedKind = 56;
+       }
+       break;
+   }
+     if (jjmatchedKind != 0x7fffffff)
+     {
+        if (jjmatchedPos + 1 < curPos)
+           input_stream.backup(curPos - jjmatchedPos - 1);
+        if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           matchedToken = jjFillToken();
+       if (jjnewLexState[jjmatchedKind] != -1)
+         curLexState = jjnewLexState[jjmatchedKind];
+           return matchedToken;
+        }
+        else
+        {
+         if (jjnewLexState[jjmatchedKind] != -1)
+           curLexState = jjnewLexState[jjmatchedKind];
+           continue EOFLoop;
+        }
+     }
+     int error_line = input_stream.getEndLine();
+     int error_column = input_stream.getEndColumn();
+     String error_after = null;
+     boolean EOFSeen = false;
+     try { input_stream.readChar(); input_stream.backup(1); }
+     catch (java.io.IOException e1) {
+        EOFSeen = true;
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+        if (curChar == '\n' || curChar == '\r') {
+           error_line++;
+           error_column = 0;
+        }
+        else
+           error_column++;
+     }
+     if (!EOFSeen) {
+        input_stream.backup(1);
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+     }
+     throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+private void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+
+private void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserTreeConstants.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserTreeConstants.java
new file mode 100644
index 0000000..ac33b5d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ELParserTreeConstants.java
@@ -0,0 +1,79 @@
+/* Generated By:JavaCC: Do not edit this line. ELParserTreeConstants.java Version 5.0 */
+package org.apache.el.parser;
+
+public interface ELParserTreeConstants
+{
+  public int JJTCOMPOSITEEXPRESSION = 0;
+  public int JJTLITERALEXPRESSION = 1;
+  public int JJTDEFERREDEXPRESSION = 2;
+  public int JJTDYNAMICEXPRESSION = 3;
+  public int JJTVOID = 4;
+  public int JJTCHOICE = 5;
+  public int JJTOR = 6;
+  public int JJTAND = 7;
+  public int JJTEQUAL = 8;
+  public int JJTNOTEQUAL = 9;
+  public int JJTLESSTHAN = 10;
+  public int JJTGREATERTHAN = 11;
+  public int JJTLESSTHANEQUAL = 12;
+  public int JJTGREATERTHANEQUAL = 13;
+  public int JJTPLUS = 14;
+  public int JJTMINUS = 15;
+  public int JJTMULT = 16;
+  public int JJTDIV = 17;
+  public int JJTMOD = 18;
+  public int JJTNEGATIVE = 19;
+  public int JJTNOT = 20;
+  public int JJTEMPTY = 21;
+  public int JJTVALUE = 22;
+  public int JJTDOTSUFFIX = 23;
+  public int JJTBRACKETSUFFIX = 24;
+  public int JJTMETHODPARAMETERS = 25;
+  public int JJTIDENTIFIER = 26;
+  public int JJTFUNCTION = 27;
+  public int JJTTRUE = 28;
+  public int JJTFALSE = 29;
+  public int JJTFLOATINGPOINT = 30;
+  public int JJTINTEGER = 31;
+  public int JJTSTRING = 32;
+  public int JJTNULL = 33;
+
+
+  public String[] jjtNodeName = {
+    "CompositeExpression",
+    "LiteralExpression",
+    "DeferredExpression",
+    "DynamicExpression",
+    "void",
+    "Choice",
+    "Or",
+    "And",
+    "Equal",
+    "NotEqual",
+    "LessThan",
+    "GreaterThan",
+    "LessThanEqual",
+    "GreaterThanEqual",
+    "Plus",
+    "Minus",
+    "Mult",
+    "Div",
+    "Mod",
+    "Negative",
+    "Not",
+    "Empty",
+    "Value",
+    "DotSuffix",
+    "BracketSuffix",
+    "MethodParameters",
+    "Identifier",
+    "Function",
+    "True",
+    "False",
+    "FloatingPoint",
+    "Integer",
+    "String",
+    "Null",
+  };
+}
+/* JavaCC - OriginalChecksum=437008e736f149e8fa6712fb36d831a1 (do not edit this line) */
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/JJTELParserState.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/JJTELParserState.java
new file mode 100644
index 0000000..9615b87
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/JJTELParserState.java
@@ -0,0 +1,123 @@
+/* Generated By:JavaCC: Do not edit this line. JJTELParserState.java Version 5.0 */
+package org.apache.el.parser;
+
+public class JJTELParserState {
+  private java.util.List<Node> nodes;
+  private java.util.List<Integer> marks;
+
+  private int sp;        // number of nodes on stack
+  private int mk;        // current mark
+  private boolean node_created;
+
+  public JJTELParserState() {
+    nodes = new java.util.ArrayList<Node>();
+    marks = new java.util.ArrayList<Integer>();
+    sp = 0;
+    mk = 0;
+  }
+
+  /* Determines whether the current node was actually closed and
+     pushed.  This should only be called in the final user action of a
+     node scope.  */
+  public boolean nodeCreated() {
+    return node_created;
+  }
+
+  /* Call this to reinitialize the node stack.  It is called
+     automatically by the parser's ReInit() method. */
+  public void reset() {
+    nodes.clear();
+    marks.clear();
+    sp = 0;
+    mk = 0;
+  }
+
+  /* Returns the root node of the AST.  It only makes sense to call
+     this after a successful parse. */
+  public Node rootNode() {
+    return nodes.get(0);
+  }
+
+  /* Pushes a node on to the stack. */
+  public void pushNode(Node n) {
+    nodes.add(n);
+    ++sp;
+  }
+
+  /* Returns the node on the top of the stack, and remove it from the
+     stack.  */
+  public Node popNode() {
+    if (--sp < mk) {
+      mk = marks.remove(marks.size()-1);
+    }
+    return nodes.remove(nodes.size()-1);
+  }
+
+  /* Returns the node currently on the top of the stack. */
+  public Node peekNode() {
+    return nodes.get(nodes.size()-1);
+  }
+
+  /* Returns the number of children on the stack in the current node
+     scope. */
+  public int nodeArity() {
+    return sp - mk;
+  }
+
+
+  public void clearNodeScope(Node n) {
+    while (sp > mk) {
+      popNode();
+    }
+    mk = marks.remove(marks.size()-1);
+  }
+
+
+  public void openNodeScope(Node n) {
+    marks.add(mk);
+    mk = sp;
+    n.jjtOpen();
+  }
+
+
+  /* A definite node is constructed from a specified number of
+     children.  That number of nodes are popped from the stack and
+     made the children of the definite node.  Then the definite node
+     is pushed on to the stack. */
+  public void closeNodeScope(Node n, int num) {
+    mk = marks.remove(marks.size()-1);
+    while (num-- > 0) {
+      Node c = popNode();
+      c.jjtSetParent(n);
+      n.jjtAddChild(c, num);
+    }
+    n.jjtClose();
+    pushNode(n);
+    node_created = true;
+  }
+
+
+  /* A conditional node is constructed if its condition is true.  All
+     the nodes that have been pushed since the node was opened are
+     made children of the conditional node, which is then pushed
+     on to the stack.  If the condition is false the node is not
+     constructed and they are left on the stack. */
+  public void closeNodeScope(Node n, boolean condition) {
+    if (condition) {
+      int a = nodeArity();
+      mk = marks.remove(marks.size()-1);
+      while (a-- > 0) {
+        Node c = popNode();
+        c.jjtSetParent(n);
+        n.jjtAddChild(c, a);
+      }
+      n.jjtClose();
+      pushNode(n);
+      node_created = true;
+    } else {
+      mk = marks.remove(marks.size()-1);
+      node_created = false;
+    }
+  }
+}
+/* JavaCC - OriginalChecksum=70ac39f1e0e1eed7476e1dae2dfa25fa (do not edit this line) */
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/Node.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/Node.java
new file mode 100644
index 0000000..1c09641
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/Node.java
@@ -0,0 +1,84 @@
+/* Generated By:JJTree: Do not edit this line. Node.java */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.el.parser;
+
+import javax.el.ELException;
+import javax.el.MethodInfo;
+import javax.el.ValueReference;
+
+import org.apache.el.lang.EvaluationContext;
+
+
+/* All AST nodes must implement this interface.  It provides basic
+   machinery for constructing the parent and child relationships
+   between nodes. */
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * $Id: Node.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public interface Node {
+
+  /** This method is called after the node has been made the current
+    node.  It indicates that child nodes can now be added to it. */
+  public void jjtOpen();
+
+  /** This method is called after all the child nodes have been
+    added. */
+  public void jjtClose();
+
+  /** This pair of methods are used to inform the node of its
+    parent. */
+  public void jjtSetParent(Node n);
+  public Node jjtGetParent();
+
+  /** This method tells the node to add its argument to the node's
+    list of children.  */
+  public void jjtAddChild(Node n, int i);
+
+  /** This method returns a child node.  The children are numbered
+     from zero, left to right. */
+  public Node jjtGetChild(int i);
+
+  /** Return the number of children the node has. */
+  public int jjtGetNumChildren();
+  
+  public String getImage();
+  
+  public Object getValue(EvaluationContext ctx) throws ELException;
+  public void setValue(EvaluationContext ctx, Object value) throws ELException;
+  public Class<?> getType(EvaluationContext ctx) throws ELException;
+  public boolean isReadOnly(EvaluationContext ctx) throws ELException;
+  public void accept(NodeVisitor visitor) throws Exception;
+  public MethodInfo getMethodInfo(EvaluationContext ctx, Class<?>[] paramTypes)
+          throws ELException;
+  public Object invoke(EvaluationContext ctx, Class<?>[] paramTypes,
+          Object[] paramValues) throws ELException;
+  
+  /**
+   * @since EL 2.2
+   */
+  public ValueReference getValueReference(EvaluationContext ctx);
+  
+  /**
+   * @since EL 2.2
+   */
+  public boolean isParametersProvided();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/NodeVisitor.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/NodeVisitor.java
new file mode 100644
index 0000000..d932a2e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/NodeVisitor.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.el.parser;
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: NodeVisitor.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public interface NodeVisitor {
+    public void visit(Node node) throws Exception;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/ParseException.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/ParseException.java
new file mode 100644
index 0000000..c89733f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/ParseException.java
@@ -0,0 +1,187 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 5.0 */
+/* JavaCCOptions:KEEP_LINE_COL=null */
+package org.apache.el.parser;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+  /**
+   * The version identifier for this Serializable class.
+   * Increment only if the <i>serialized</i> form of the
+   * class changes.
+   */
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super(initialise(currentTokenVal, expectedTokenSequencesVal, tokenImageVal));
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
+
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
+
+  public ParseException() {
+    super();
+  }
+
+  /** Constructor with message. */
+  public ParseException(String message) {
+    super(message);
+  }
+
+
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
+
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
+
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * It uses "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser) the correct error message
+   * gets displayed.
+   */
+  private static String initialise(Token currentToken,
+                           int[][] expectedTokenSequences,
+                           String[] tokenImage) {
+    String eol = System.getProperty("line.separator", "\n");
+    StringBuffer expected = new StringBuffer();
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected.append(tokenImage[expectedTokenSequences[i][j]]).append(' ');
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+        expected.append("...");
+      }
+      expected.append(eol).append("    ");
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += " " + tokenImage[tok.kind];
+      retval += " \"";
+      retval += add_escapes(tok.image);
+      retval += " \"";
+      tok = tok.next;
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
+    }
+    retval += expected.toString();
+    return retval;
+  }
+
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  static String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+}
+/* JavaCC - OriginalChecksum=87586a39aa89f164889cc59bc6a7e7ad (do not edit this line) */
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/SimpleCharStream.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/SimpleCharStream.java
new file mode 100644
index 0000000..e484965
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/SimpleCharStream.java
@@ -0,0 +1,471 @@
+/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 5.0 */
+/* JavaCCOptions:STATIC=false,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
+package org.apache.el.parser;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public class SimpleCharStream
+{
+/** Whether parser is static. */
+  public static final boolean staticFlag = false;
+  int bufsize;
+  int available;
+  int tokenBegin;
+/** Position in buffer. */
+  public int bufpos = -1;
+  protected int bufline[];
+  protected int bufcolumn[];
+
+  protected int column = 0;
+  protected int line = 1;
+
+  protected boolean prevCharIsCR = false;
+  protected boolean prevCharIsLF = false;
+
+  protected java.io.Reader inputStream;
+
+  protected char[] buffer;
+  protected int maxNextCharInd = 0;
+  protected int inBuf = 0;
+  protected int tabSize = 8;
+
+  protected void setTabSize(int i) { tabSize = i; }
+  protected int getTabSize(int i) { return tabSize; }
+
+
+  protected void ExpandBuff(boolean wrapAround)
+  {
+    char[] newbuffer = new char[bufsize + 2048];
+    int newbufline[] = new int[bufsize + 2048];
+    int newbufcolumn[] = new int[bufsize + 2048];
+
+    try
+    {
+      if (wrapAround)
+      {
+        System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+        System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
+        buffer = newbuffer;
+
+        System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+        System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+        bufline = newbufline;
+
+        System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+        System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+        bufcolumn = newbufcolumn;
+
+        maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+      }
+      else
+      {
+        System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+        buffer = newbuffer;
+
+        System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+        bufline = newbufline;
+
+        System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+        bufcolumn = newbufcolumn;
+
+        maxNextCharInd = (bufpos -= tokenBegin);
+      }
+    }
+    catch (Throwable t)
+    {
+      throw new Error(t.getMessage());
+    }
+
+
+    bufsize += 2048;
+    available = bufsize;
+    tokenBegin = 0;
+  }
+
+  protected void FillBuff() throws java.io.IOException
+  {
+    if (maxNextCharInd == available)
+    {
+      if (available == bufsize)
+      {
+        if (tokenBegin > 2048)
+        {
+          bufpos = maxNextCharInd = 0;
+          available = tokenBegin;
+        }
+        else if (tokenBegin < 0)
+          bufpos = maxNextCharInd = 0;
+        else
+          ExpandBuff(false);
+      }
+      else if (available > tokenBegin)
+        available = bufsize;
+      else if ((tokenBegin - available) < 2048)
+        ExpandBuff(true);
+      else
+        available = tokenBegin;
+    }
+
+    int i;
+    try {
+      if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1)
+      {
+        inputStream.close();
+        throw new java.io.IOException();
+      }
+      else
+        maxNextCharInd += i;
+      return;
+    }
+    catch(java.io.IOException e) {
+      --bufpos;
+      backup(0);
+      if (tokenBegin == -1)
+        tokenBegin = bufpos;
+      throw e;
+    }
+  }
+
+/** Start. */
+  public char BeginToken() throws java.io.IOException
+  {
+    tokenBegin = -1;
+    char c = readChar();
+    tokenBegin = bufpos;
+
+    return c;
+  }
+
+  protected void UpdateLineColumn(char c)
+  {
+    column++;
+
+    if (prevCharIsLF)
+    {
+      prevCharIsLF = false;
+      line += (column = 1);
+    }
+    else if (prevCharIsCR)
+    {
+      prevCharIsCR = false;
+      if (c == '\n')
+      {
+        prevCharIsLF = true;
+      }
+      else
+        line += (column = 1);
+    }
+
+    switch (c)
+    {
+      case '\r' :
+        prevCharIsCR = true;
+        break;
+      case '\n' :
+        prevCharIsLF = true;
+        break;
+      case '\t' :
+        column--;
+        column += (tabSize - (column % tabSize));
+        break;
+      default :
+        break;
+    }
+
+    bufline[bufpos] = line;
+    bufcolumn[bufpos] = column;
+  }
+
+/** Read a character. */
+  public char readChar() throws java.io.IOException
+  {
+    if (inBuf > 0)
+    {
+      --inBuf;
+
+      if (++bufpos == bufsize)
+        bufpos = 0;
+
+      return buffer[bufpos];
+    }
+
+    if (++bufpos >= maxNextCharInd)
+      FillBuff();
+
+    char c = buffer[bufpos];
+
+    UpdateLineColumn(c);
+    return c;
+  }
+
+  @Deprecated
+  /**
+   * @deprecated
+   * @see #getEndColumn
+   */
+
+  public int getColumn() {
+    return bufcolumn[bufpos];
+  }
+
+  @Deprecated
+  /**
+   * @deprecated
+   * @see #getEndLine
+   */
+
+  public int getLine() {
+    return bufline[bufpos];
+  }
+
+  /** Get token end column number. */
+  public int getEndColumn() {
+    return bufcolumn[bufpos];
+  }
+
+  /** Get token end line number. */
+  public int getEndLine() {
+     return bufline[bufpos];
+  }
+
+  /** Get token beginning column number. */
+  public int getBeginColumn() {
+    return bufcolumn[tokenBegin];
+  }
+
+  /** Get token beginning line number. */
+  public int getBeginLine() {
+    return bufline[tokenBegin];
+  }
+
+/** Backup a number of characters. */
+  public void backup(int amount) {
+
+    inBuf += amount;
+    if ((bufpos -= amount) < 0)
+      bufpos += bufsize;
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    available = bufsize = buffersize;
+    buffer = new char[buffersize];
+    bufline = new int[buffersize];
+    bufcolumn = new int[buffersize];
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+                          int startcolumn)
+  {
+    this(dstream, startline, startcolumn, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.Reader dstream)
+  {
+    this(dstream, 1, 1, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    if (buffer == null || buffersize != buffer.length)
+    {
+      available = bufsize = buffersize;
+      buffer = new char[buffersize];
+      bufline = new int[buffersize];
+      bufcolumn = new int[buffersize];
+    }
+    prevCharIsLF = prevCharIsCR = false;
+    tokenBegin = inBuf = maxNextCharInd = 0;
+    bufpos = -1;
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader dstream, int startline,
+                     int startcolumn)
+  {
+    ReInit(dstream, startline, startcolumn, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader dstream)
+  {
+    ReInit(dstream, 1, 1, 4096);
+  }
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+    this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+    this(dstream, encoding, startline, startcolumn, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+                          int startcolumn)
+  {
+    this(dstream, startline, startcolumn, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+    this(dstream, encoding, 1, 1, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream)
+  {
+    this(dstream, 1, 1, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+    ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize)
+  {
+    ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+    ReInit(dstream, encoding, 1, 1, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream)
+  {
+    ReInit(dstream, 1, 1, 4096);
+  }
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                     int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+    ReInit(dstream, encoding, startline, startcolumn, 4096);
+  }
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, int startline,
+                     int startcolumn)
+  {
+    ReInit(dstream, startline, startcolumn, 4096);
+  }
+  /** Get token literal value. */
+  public String GetImage()
+  {
+    if (bufpos >= tokenBegin)
+      return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+    else
+      return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                            new String(buffer, 0, bufpos + 1);
+  }
+
+  /** Get the suffix. */
+  public char[] GetSuffix(int len)
+  {
+    char[] ret = new char[len];
+
+    if ((bufpos + 1) >= len)
+      System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+    else
+    {
+      System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                                                        len - bufpos - 1);
+      System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+    }
+
+    return ret;
+  }
+
+  /** Reset buffer when finished. */
+  public void Done()
+  {
+    buffer = null;
+    bufline = null;
+    bufcolumn = null;
+  }
+
+  /**
+   * Method to adjust line and column numbers for the start of a token.
+   */
+  public void adjustBeginLineColumn(int newLine, int newCol)
+  {
+    int start = tokenBegin;
+    int len;
+
+    if (bufpos >= tokenBegin)
+    {
+      len = bufpos - tokenBegin + inBuf + 1;
+    }
+    else
+    {
+      len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+    }
+
+    int i = 0, j = 0, k = 0;
+    int nextColDiff = 0, columnDiff = 0;
+
+    while (i < len && bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+    {
+      bufline[j] = newLine;
+      nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+      bufcolumn[j] = newCol + columnDiff;
+      columnDiff = nextColDiff;
+      i++;
+    }
+
+    if (i < len)
+    {
+      bufline[j] = newLine++;
+      bufcolumn[j] = newCol + columnDiff;
+
+      while (i++ < len)
+      {
+        if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+          bufline[j] = newLine++;
+        else
+          bufline[j] = newLine;
+      }
+    }
+
+    line = bufline[j];
+    column = bufcolumn[j];
+  }
+
+}
+/* JavaCC - OriginalChecksum=9ba0db3918bffb8019f00da1e421f339 (do not edit this line) */
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/SimpleNode.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/SimpleNode.java
new file mode 100644
index 0000000..36655a6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/SimpleNode.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Generated By:JJTree: Do not edit this line. SimpleNode.java */
+
+package org.apache.el.parser;
+
+import java.util.Arrays;
+
+import javax.el.ELException;
+import javax.el.MethodInfo;
+import javax.el.PropertyNotWritableException;
+import javax.el.ValueReference;
+
+import org.apache.el.lang.ELSupport;
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.util.MessageFactory;
+
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: SimpleNode.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public abstract class SimpleNode extends ELSupport implements Node {
+    protected Node parent;
+
+    protected Node[] children;
+
+    protected int id;
+
+    protected String image;
+
+    public SimpleNode(int i) {
+        id = i;
+    }
+
+    @Override
+    public void jjtOpen() {
+        // NOOP by default
+    }
+
+    @Override
+    public void jjtClose() {
+        // NOOP by default
+    }
+
+    @Override
+    public void jjtSetParent(Node n) {
+        parent = n;
+    }
+
+    @Override
+    public Node jjtGetParent() {
+        return parent;
+    }
+
+    @Override
+    public void jjtAddChild(Node n, int i) {
+        if (children == null) {
+            children = new Node[i + 1];
+        } else if (i >= children.length) {
+            Node c[] = new Node[i + 1];
+            System.arraycopy(children, 0, c, 0, children.length);
+            children = c;
+        }
+        children[i] = n;
+    }
+
+    @Override
+    public Node jjtGetChild(int i) {
+        return children[i];
+    }
+
+    @Override
+    public int jjtGetNumChildren() {
+        return (children == null) ? 0 : children.length;
+    }
+
+    /*
+     * You can override these two methods in subclasses of SimpleNode to
+     * customize the way the node appears when the tree is dumped. If your
+     * output uses more than one line you should override toString(String),
+     * otherwise overriding toString() is probably all you need to do.
+     */
+
+    @Override
+    public String toString() {
+        if (this.image != null) {
+            return ELParserTreeConstants.jjtNodeName[id] + "[" + this.image
+                    + "]";
+        }
+        return ELParserTreeConstants.jjtNodeName[id];
+    }
+
+    public String toString(String prefix) {
+        return prefix + toString();
+    }
+
+    @Override
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    @Override
+    public Class<?> getType(EvaluationContext ctx)
+            throws ELException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isReadOnly(EvaluationContext ctx)
+            throws ELException {
+        return true;
+    }
+
+    @Override
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        throw new PropertyNotWritableException(MessageFactory.get("error.syntax.set"));
+    }
+
+    @Override
+    public void accept(NodeVisitor visitor) throws Exception {
+        visitor.visit(this);
+        if (this.children != null && this.children.length > 0) {
+            for (int i = 0; i < this.children.length; i++) {
+                this.children[i].accept(visitor);
+            }
+        }
+    }
+
+    @Override
+    public Object invoke(EvaluationContext ctx, Class<?>[] paramTypes,
+            Object[] paramValues) throws ELException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public MethodInfo getMethodInfo(EvaluationContext ctx,
+            Class<?>[] paramTypes) throws ELException {
+        throw new UnsupportedOperationException();
+    }
+    
+    
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(children);
+        result = prime * result + id;
+        result = prime * result + ((image == null) ? 0 : image.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof SimpleNode)) {
+            return false;
+        }
+        SimpleNode other = (SimpleNode) obj;
+        if (!Arrays.equals(children, other.children)) {
+            return false;
+        }
+        if (id != other.id) {
+            return false;
+        }
+        if (image == null) {
+            if (other.image != null) {
+                return false;
+            }
+        } else if (!image.equals(other.image)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @since EL 2.2
+     */
+    @Override
+    public ValueReference getValueReference(EvaluationContext ctx) {
+        return null;
+    }
+    
+    /**
+     * @since EL 2.2
+     */
+    @Override
+    public boolean isParametersProvided() {
+        return false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/Token.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/Token.java
new file mode 100644
index 0000000..3aa02cc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/Token.java
@@ -0,0 +1,131 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */
+/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
+package org.apache.el.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token implements java.io.Serializable {
+
+  /**
+   * The version identifier for this Serializable class.
+   * Increment only if the <i>serialized</i> form of the
+   * class changes.
+   */
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * An integer that describes the kind of this token.  This numbering
+   * system is determined by JavaCCParser, and a table of these numbers is
+   * stored in the file ...Constants.java.
+   */
+  public int kind;
+
+  /** The line number of the first character of this Token. */
+  public int beginLine;
+  /** The column number of the first character of this Token. */
+  public int beginColumn;
+  /** The line number of the last character of this Token. */
+  public int endLine;
+  /** The column number of the last character of this Token. */
+  public int endColumn;
+
+  /**
+   * The string image of the token.
+   */
+  public String image;
+
+  /**
+   * A reference to the next regular (non-special) token from the input
+   * stream.  If this is the last token from the input stream, or if the
+   * token manager has not read tokens beyond this one, this field is
+   * set to null.  This is true only if this token is also a regular
+   * token.  Otherwise, see below for a description of the contents of
+   * this field.
+   */
+  public Token next;
+
+  /**
+   * This field is used to access special tokens that occur prior to this
+   * token, but after the immediately preceding regular (non-special) token.
+   * If there are no such special tokens, this field is set to null.
+   * When there are more than one such special token, this field refers
+   * to the last of these special tokens, which in turn refers to the next
+   * previous special token through its specialToken field, and so on
+   * until the first special token (whose specialToken field is null).
+   * The next fields of special tokens refer to other special tokens that
+   * immediately follow it (without an intervening regular token).  If there
+   * is no such token, this field is null.
+   */
+  public Token specialToken;
+
+  /**
+   * An optional attribute value of the Token.
+   * Tokens which are not used as syntactic sugar will often contain
+   * meaningful values that will be used later on by the compiler or
+   * interpreter. This attribute value is often different from the image.
+   * Any subclass of Token that actually wants to return a non-null value can
+   * override this method as appropriate.
+   */
+  public Object getValue() {
+    return null;
+  }
+
+  /**
+   * No-argument constructor
+   */
+  public Token() {}
+
+  /**
+   * Constructs a new token for the specified Image.
+   */
+  public Token(int kind)
+  {
+    this(kind, null);
+  }
+
+  /**
+   * Constructs a new token for the specified Image and Kind.
+   */
+  public Token(int kind, String image)
+  {
+    this.kind = kind;
+    this.image = image;
+  }
+
+  /**
+   * Returns the image.
+   */
+  public String toString()
+  {
+    return image;
+  }
+
+  /**
+   * Returns a new Token object, by default. However, if you want, you
+   * can create and return subclass objects based on the value of ofKind.
+   * Simply add the cases to the switch for all those special cases.
+   * For example, if you have a subclass of Token called IDToken that
+   * you want to create if ofKind is ID, simply add something like :
+   *
+   *    case MyParserConstants.ID : return new IDToken(ofKind, image);
+   *
+   * to the following switch statement. Then you can cast matchedToken
+   * variable to the appropriate type and use sit in your lexical actions.
+   */
+  public static Token newToken(int ofKind, String image)
+  {
+    switch(ofKind)
+    {
+      default : return new Token(ofKind, image);
+    }
+  }
+
+  public static Token newToken(int ofKind)
+  {
+    return newToken(ofKind, null);
+  }
+
+}
+/* JavaCC - OriginalChecksum=3fc97649fffa8b13e1e03af022020b2f (do not edit this line) */
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/parser/TokenMgrError.java b/bundles/org.apache.tomcat/src/org/apache/el/parser/TokenMgrError.java
new file mode 100644
index 0000000..2964ed0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/parser/TokenMgrError.java
@@ -0,0 +1,147 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */
+/* JavaCCOptions: */
+package org.apache.el.parser;
+
+/** Token Manager Error. */
+public class TokenMgrError extends Error
+{
+
+  /**
+   * The version identifier for this Serializable class.
+   * Increment only if the <i>serialized</i> form of the
+   * class changes.
+   */
+  private static final long serialVersionUID = 1L;
+
+  /*
+   * Ordinals for various reasons why an Error of this type can be thrown.
+   */
+
+  /**
+   * Lexical error occurred.
+   */
+  static final int LEXICAL_ERROR = 0;
+
+  /**
+   * An attempt was made to create a second instance of a static token manager.
+   */
+  static final int STATIC_LEXER_ERROR = 1;
+
+  /**
+   * Tried to change to an invalid lexical state.
+   */
+  static final int INVALID_LEXICAL_STATE = 2;
+
+  /**
+   * Detected (and bailed out of) an infinite loop in the token manager.
+   */
+  static final int LOOP_DETECTED = 3;
+
+  /**
+   * Indicates the reason why the exception is thrown. It will have
+   * one of the above 4 values.
+   */
+  int errorCode;
+
+  /**
+   * Replaces unprintable characters by their escaped (or unicode escaped)
+   * equivalents in the given string
+   */
+  protected static final String addEscapes(String str) {
+    StringBuffer retval = new StringBuffer();
+    char ch;
+    for (int i = 0; i < str.length(); i++) {
+      switch (str.charAt(i))
+      {
+        case 0 :
+          continue;
+        case '\b':
+          retval.append("\\b");
+          continue;
+        case '\t':
+          retval.append("\\t");
+          continue;
+        case '\n':
+          retval.append("\\n");
+          continue;
+        case '\f':
+          retval.append("\\f");
+          continue;
+        case '\r':
+          retval.append("\\r");
+          continue;
+        case '\"':
+          retval.append("\\\"");
+          continue;
+        case '\'':
+          retval.append("\\\'");
+          continue;
+        case '\\':
+          retval.append("\\\\");
+          continue;
+        default:
+          if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+            String s = "0000" + Integer.toString(ch, 16);
+            retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+          } else {
+            retval.append(ch);
+          }
+          continue;
+      }
+    }
+    return retval.toString();
+  }
+
+  /**
+   * Returns a detailed message for the Error when it is thrown by the
+   * token manager to indicate a lexical error.
+   * Parameters :
+   *    EOFSeen     : indicates if EOF caused the lexical error
+   *    curLexState : lexical state in which this error occurred
+   *    errorLine   : line number when the error occurred
+   *    errorColumn : column number when the error occurred
+   *    errorAfter  : prefix that was seen before this error occurred
+   *    curchar     : the offending character
+   * Note: You can customize the lexical error message by modifying this method.
+   */
+  protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+    return("Lexical error at line " +
+          errorLine + ", column " +
+          errorColumn + ".  Encountered: " +
+          (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+          "after : \"" + addEscapes(errorAfter) + "\"");
+  }
+
+  /**
+   * You can also modify the body of this method to customize your error messages.
+   * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+   * of end-users concern, so you can return something like :
+   *
+   *     "Internal Error : Please file a bug report .... "
+   *
+   * from this method for such cases in the release version of your parser.
+   */
+  public String getMessage() {
+    return super.getMessage();
+  }
+
+  /*
+   * Constructors of various flavors follow.
+   */
+
+  /** No arg constructor. */
+  public TokenMgrError() {
+  }
+
+  /** Constructor with message and reason. */
+  public TokenMgrError(String message, int reason) {
+    super(message);
+    errorCode = reason;
+  }
+
+  /** Full Constructor. */
+  public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+    this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+  }
+}
+/* JavaCC - OriginalChecksum=de3ff0bacfb0fe749cc8eaf56ae82fea (do not edit this line) */
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/Constants.java b/bundles/org.apache.tomcat/src/org/apache/jasper/Constants.java
new file mode 100644
index 0000000..60ff5e3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/Constants.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Some constants and other global data that are used by the compiler and the runtime.
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+public class Constants {
+    
+    /**
+     * The base class of the generated servlets. 
+     */
+    public static final String JSP_SERVLET_BASE = 
+        System.getProperty("org.apache.jasper.Constants.JSP_SERVLET_BASE", "org.apache.jasper.runtime.HttpJspBase");
+
+    /**
+     * _jspService is the name of the method that is called by 
+     * HttpJspBase.service(). This is where most of the code generated
+     * from JSPs go.
+     */
+    public static final String SERVICE_METHOD_NAME = 
+        System.getProperty("org.apache.jasper.Constants.SERVICE_METHOD_NAME", "_jspService");
+
+    /**
+     * Default servlet content type.
+     */
+    public static final String SERVLET_CONTENT_TYPE = "text/html";
+
+    /**
+     * These classes/packages are automatically imported by the
+     * generated code. 
+     */
+    private static final String[] PRIVATE_STANDARD_IMPORTS = { 
+        "javax.servlet.*", 
+        "javax.servlet.http.*", 
+        "javax.servlet.jsp.*"
+    };
+    public static final List<String> STANDARD_IMPORTS =
+        Collections.unmodifiableList(Arrays.asList(PRIVATE_STANDARD_IMPORTS));
+
+    /**
+     * ServletContext attribute for classpath. This is tomcat specific. 
+     * Other servlet engines may choose to support this attribute if they 
+     * want to have this JSP engine running on them. 
+     */
+    public static final String SERVLET_CLASSPATH = 
+        System.getProperty("org.apache.jasper.Constants.SERVLET_CLASSPATH", "org.apache.catalina.jsp_classpath");
+
+    /**
+     * Request attribute for <code>&lt;jsp-file&gt;</code> element of a
+     * servlet definition.  If present on a request, this overrides the
+     * value returned by <code>request.getServletPath()</code> to select
+     * the JSP page to be executed.
+     */
+    public static final String JSP_FILE = 
+        System.getProperty("org.apache.jasper.Constants.JSP_FILE", "org.apache.catalina.jsp_file");
+
+
+    /**
+     * Default size of the JSP buffer.
+     */
+    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+
+    /**
+     * Default size for the tag buffers.
+     */
+    public static final int DEFAULT_TAG_BUFFER_SIZE = 512;
+
+    /**
+     * Default tag handler pool size.
+     */
+    public static final int MAX_POOL_SIZE = 5;
+
+    /**
+     * Platform specific new line sequence.
+     */
+    public static final String NEWLINE = System.getProperty("line.separator");
+
+    /**
+     * The query parameter that causes the JSP engine to just
+     * pregenerated the servlet but not invoke it. 
+     */
+    public static final String PRECOMPILE = 
+        System.getProperty("org.apache.jasper.Constants.PRECOMPILE", "jsp_precompile");
+
+    /**
+     * The default package name for compiled jsp pages.
+     */
+    public static final String JSP_PACKAGE_NAME = 
+        System.getProperty("org.apache.jasper.Constants.JSP_PACKAGE_NAME", "org.apache.jsp");
+
+    /**
+     * The default package name for tag handlers generated from tag files
+     */
+    public static final String TAG_FILE_PACKAGE_NAME = 
+        System.getProperty("org.apache.jasper.Constants.TAG_FILE_PACKAGE_NAME", "org.apache.jsp.tag");
+
+    // Must be kept in sync with org/apache/catalina/Globals.java
+    public static final String ALT_DD_ATTR = 
+        System.getProperty("org.apache.jasper.Constants.ALT_DD_ATTR", "org.apache.catalina.deploy.alt_dd");
+
+    /**
+     * Public Id and the Resource path (of the cached copy) 
+     * of the DTDs for tag library descriptors. 
+     */
+    public static final String TAGLIB_DTD_PUBLIC_ID_11 = 
+        "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN";
+    public static final String TAGLIB_DTD_RESOURCE_PATH_11 = 
+        "/javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd";
+    public static final String TAGLIB_DTD_PUBLIC_ID_12 = 
+        "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN";
+    public static final String TAGLIB_DTD_RESOURCE_PATH_12 = 
+        "/javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd";
+
+    /**
+     * Public Id and the Resource path (of the cached copy) 
+     * of the DTDs for web application deployment descriptors
+     */
+    public static final String WEBAPP_DTD_PUBLIC_ID_22 = 
+        "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN";
+    public static final String WEBAPP_DTD_RESOURCE_PATH_22 = 
+        "/javax/servlet/resources/web-app_2_2.dtd";
+    public static final String WEBAPP_DTD_PUBLIC_ID_23 = 
+        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN";
+    public static final String WEBAPP_DTD_RESOURCE_PATH_23 = 
+        "/javax/servlet/resources/web-app_2_3.dtd";
+
+    /**
+     * List of the Public IDs that we cache, and their
+     * associated location. This is used by 
+     * an EntityResolver to return the location of the
+     * cached copy of a DTD.
+     */
+    // TODO Add 2.4, 2.5, 3.0
+    private static final String[] PRIVATE_CACHED_DTD_PUBLIC_IDS = {
+        TAGLIB_DTD_PUBLIC_ID_11,
+        TAGLIB_DTD_PUBLIC_ID_12,
+        WEBAPP_DTD_PUBLIC_ID_22,
+        WEBAPP_DTD_PUBLIC_ID_23,
+    };
+    public static final List<String> CACHED_DTD_PUBLIC_IDS =
+        Collections.unmodifiableList(
+                Arrays.asList(PRIVATE_CACHED_DTD_PUBLIC_IDS));
+    private static final String[] PRIVATE_CACHED_DTD_RESOURCE_PATHS = {
+        TAGLIB_DTD_RESOURCE_PATH_11,
+        TAGLIB_DTD_RESOURCE_PATH_12,
+        WEBAPP_DTD_RESOURCE_PATH_22,
+        WEBAPP_DTD_RESOURCE_PATH_23,
+    };
+    public static final List<String> CACHED_DTD_RESOURCE_PATHS =
+        Collections.unmodifiableList(
+                Arrays.asList(PRIVATE_CACHED_DTD_RESOURCE_PATHS));
+    
+    /**
+     * Default URLs to download the plugin for Netscape and IE.
+     */
+    public static final String NS_PLUGIN_URL = 
+        "http://java.sun.com/products/plugin/";
+
+    public static final String IE_PLUGIN_URL = 
+        "http://java.sun.com/products/plugin/1.2.2/jinstall-1_2_2-win.cab#Version=1,2,2,0";
+
+    /**
+     * Prefix to use for generated temporary variable names
+     */
+    public static final String TEMP_VARIABLE_NAME_PREFIX =
+        System.getProperty("org.apache.jasper.Constants.TEMP_VARIABLE_NAME_PREFIX", "_jspx_temp");
+
+    /**
+     * Has security been turned on?
+     */
+    public static final boolean IS_SECURITY_ENABLED = 
+        (System.getSecurityManager() != null);
+
+    public static final boolean USE_INSTANCE_MANAGER_FOR_TAGS =
+        Boolean.valueOf(System.getProperty("org.apache.jasper.Constants.USE_INSTANCE_MANAGER_FOR_TAGS", "false")).booleanValue();
+
+    /**
+     * The name of the path parameter used to pass the session identifier
+     * back and forth with the client.
+     */
+    public static final String SESSION_PARAMETER_NAME =
+        System.getProperty("org.apache.catalina.SESSION_PARAMETER_NAME",
+                "jsessionid");
+
+    /**
+     * Name of the system property containing
+     * the tomcat product installation path
+     */
+    public static final String CATALINA_HOME_PROP = "catalina.home";
+
+    /**
+     * Name of the system property containing
+     * the tomcat instance installation path
+     */
+    public static final String CATALINA_BASE_PROP = "catalina.base";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/EmbeddedServletOptions.java b/bundles/org.apache.tomcat/src/org/apache/jasper/EmbeddedServletOptions.java
new file mode 100644
index 0000000..0ab63ab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/EmbeddedServletOptions.java
@@ -0,0 +1,761 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+import org.apache.jasper.compiler.JspConfig;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.compiler.TagPluginManager;
+import org.apache.jasper.compiler.TldLocationsCache;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * A class to hold all init parameters specific to the JSP engine. 
+ *
+ * @author Anil K. Vijendran
+ * @author Hans Bergsten
+ * @author Pierre Delisle
+ */
+public final class EmbeddedServletOptions implements Options {
+    
+    // Logger
+    private final Log log = LogFactory.getLog(EmbeddedServletOptions.class);
+    
+    private Properties settings = new Properties();
+    
+    /**
+     * Is Jasper being used in development mode?
+     */
+    private boolean development = true;
+    
+    /**
+     * Should Ant fork its java compiles of JSP pages.
+     */
+    public boolean fork = true;
+    
+    /**
+     * Do you want to keep the generated Java files around?
+     */
+    private boolean keepGenerated = true;
+    
+    /**
+     * Should white spaces between directives or actions be trimmed?
+     */
+    private boolean trimSpaces = false;
+    
+    /**
+     * Determines whether tag handler pooling is enabled.
+     */
+    private boolean isPoolingEnabled = true;
+    
+    /**
+     * Do you want support for "mapped" files? This will generate
+     * servlet that has a print statement per line of the JSP file.
+     * This seems like a really nice feature to have for debugging.
+     */
+    private boolean mappedFile = true;
+    
+    /**
+     * Do we want to include debugging information in the class file?
+     */
+    private boolean classDebugInfo = true;
+    
+    /**
+     * Background compile thread check interval in seconds.
+     */
+    private int checkInterval = 0;
+    
+    /**
+     * Is the generation of SMAP info for JSR45 debugging suppressed?
+     */
+    private boolean isSmapSuppressed = false;
+    
+    /**
+     * Should SMAP info for JSR45 debugging be dumped to a file?
+     */
+    private boolean isSmapDumped = false;
+    
+    /**
+     * Are Text strings to be generated as char arrays?
+     */
+    private boolean genStringAsCharArray = false;
+    
+    private boolean errorOnUseBeanInvalidClassAttribute = true;
+    
+    /**
+     * I want to see my generated servlets. Which directory are they
+     * in?
+     */
+    private File scratchDir;
+    
+    /**
+     * Need to have this as is for versions 4 and 5 of IE. Can be set from
+     * the initParams so if it changes in the future all that is needed is
+     * to have a jsp initParam of type ieClassId="<value>"
+     */
+    private String ieClassId = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93";
+    
+    /**
+     * What classpath should I use while compiling generated servlets?
+     */
+    private String classpath = null;
+    
+    /**
+     * Compiler to use.
+     */
+    private String compiler = null;
+    
+    /**
+     * Compiler target VM.
+     */
+    private String compilerTargetVM = "1.6";
+    
+    /**
+     * The compiler source VM.
+     */
+    private String compilerSourceVM = "1.6";
+    
+    /**
+     * The compiler class name.
+     */
+    private String compilerClassName = null;
+    
+    /**
+     * Cache for the TLD locations
+     */
+    private TldLocationsCache tldLocationsCache = null;
+    
+    /**
+     * Jsp config information
+     */
+    private JspConfig jspConfig = null;
+    
+    /**
+     * TagPluginManager
+     */
+    private TagPluginManager tagPluginManager = null;
+    
+    /**
+     * Java platform encoding to generate the JSP
+     * page servlet.
+     */
+    private String javaEncoding = "UTF8";
+    
+    /**
+     * Modification test interval.
+     */
+    private int modificationTestInterval = 4;
+    
+    /**
+     * Is re-compilation attempted immediately after a failure?
+     */
+    private boolean recompileOnFail = false;
+    
+    /**
+     * Is generation of X-Powered-By response header enabled/disabled?
+     */
+    private boolean xpoweredBy;
+    
+    /**
+     * Should we include a source fragment in exception messages, which could be displayed
+     * to the developer ?
+     */
+    private boolean displaySourceFragment = true;
+
+    
+    /**
+     * The maximum number of loaded jsps per web-application. If there are more
+     * jsps loaded, they will be unloaded.
+     */
+    private int maxLoadedJsps = -1;
+
+    /**
+     * The idle time in seconds after which a JSP is unloaded.
+     * If unset or less or equal than 0, no jsps are unloaded.
+     */
+    private int jspIdleTimeout = -1;
+
+    public String getProperty(String name ) {
+        return settings.getProperty( name );
+    }
+    
+    public void setProperty(String name, String value ) {
+        if (name != null && value != null){ 
+            settings.setProperty( name, value );
+        }
+    }
+    
+    /**
+     * Are we keeping generated code around?
+     */
+    @Override
+    public boolean getKeepGenerated() {
+        return keepGenerated;
+    }
+    
+    /**
+     * Should white spaces between directives or actions be trimmed?
+     */
+    @Override
+    public boolean getTrimSpaces() {
+        return trimSpaces;
+    }
+    
+    @Override
+    public boolean isPoolingEnabled() {
+        return isPoolingEnabled;
+    }
+    
+    /**
+     * Are we supporting HTML mapped servlets?
+     */
+    @Override
+    public boolean getMappedFile() {
+        return mappedFile;
+    }
+    
+    /**
+     * Should class files be compiled with debug information?
+     */
+    @Override
+    public boolean getClassDebugInfo() {
+        return classDebugInfo;
+    }
+    
+    /**
+     * Background JSP compile thread check interval
+     */
+    @Override
+    public int getCheckInterval() {
+        return checkInterval;
+    }
+    
+    /**
+     * Modification test interval.
+     */
+    @Override
+    public int getModificationTestInterval() {
+        return modificationTestInterval;
+    }
+    
+    /**
+     * Re-compile on failure.
+     */
+    @Override
+    public boolean getRecompileOnFail() {
+        return recompileOnFail;
+    }
+    
+    /**
+     * Is Jasper being used in development mode?
+     */
+    @Override
+    public boolean getDevelopment() {
+        return development;
+    }
+    
+    /**
+     * Is the generation of SMAP info for JSR45 debugging suppressed?
+     */
+    @Override
+    public boolean isSmapSuppressed() {
+        return isSmapSuppressed;
+    }
+    
+    /**
+     * Should SMAP info for JSR45 debugging be dumped to a file?
+     */
+    @Override
+    public boolean isSmapDumped() {
+        return isSmapDumped;
+    }
+    
+    /**
+     * Are Text strings to be generated as char arrays?
+     */
+    @Override
+    public boolean genStringAsCharArray() {
+        return this.genStringAsCharArray;
+    }
+    
+    /**
+     * Class ID for use in the plugin tag when the browser is IE. 
+     */
+    @Override
+    public String getIeClassId() {
+        return ieClassId;
+    }
+    
+    /**
+     * What is my scratch dir?
+     */
+    @Override
+    public File getScratchDir() {
+        return scratchDir;
+    }
+    
+    /**
+     * What classpath should I use while compiling the servlets
+     * generated from JSP files?
+     */
+    @Override
+    public String getClassPath() {
+        return classpath;
+    }
+    
+    /**
+     * Is generation of X-Powered-By response header enabled/disabled?
+     */
+    @Override
+    public boolean isXpoweredBy() {
+        return xpoweredBy;
+    }
+    
+    /**
+     * Compiler to use.
+     */
+    @Override
+    public String getCompiler() {
+        return compiler;
+    }
+    
+    /**
+     * @see Options#getCompilerTargetVM
+     */
+    @Override
+    public String getCompilerTargetVM() {
+        return compilerTargetVM;
+    }
+    
+    /**
+     * @see Options#getCompilerSourceVM
+     */
+    @Override
+    public String getCompilerSourceVM() {
+        return compilerSourceVM;
+    }
+    
+    /**
+     * Java compiler class to use.
+     */
+    @Override
+    public String getCompilerClassName() {
+        return compilerClassName;
+    }
+
+    @Override
+    public boolean getErrorOnUseBeanInvalidClassAttribute() {
+        return errorOnUseBeanInvalidClassAttribute;
+    }
+    
+    public void setErrorOnUseBeanInvalidClassAttribute(boolean b) {
+        errorOnUseBeanInvalidClassAttribute = b;
+    }
+    
+    @Override
+    public TldLocationsCache getTldLocationsCache() {
+        return tldLocationsCache;
+    }
+    
+    public void setTldLocationsCache( TldLocationsCache tldC ) {
+        tldLocationsCache = tldC;
+    }
+    
+    @Override
+    public String getJavaEncoding() {
+        return javaEncoding;
+    }
+    
+    @Override
+    public boolean getFork() {
+        return fork;
+    }
+    
+    @Override
+    public JspConfig getJspConfig() {
+        return jspConfig;
+    }
+    
+    @Override
+    public TagPluginManager getTagPluginManager() {
+        return tagPluginManager;
+    }
+    
+    @Override
+    public boolean isCaching() {
+        return false;
+    }
+    
+    @Override
+    public Map<String, TagLibraryInfo> getCache() {
+        return null;
+    }
+
+    /**
+     * Should we include a source fragment in exception messages, which could be displayed
+     * to the developer ?
+     */
+    @Override
+    public boolean getDisplaySourceFragment() {
+        return displaySourceFragment;
+    }
+
+    /**
+     * Should jsps be unloaded if to many are loaded?
+     * If set to a value greater than 0 eviction of jsps is started. Default: -1
+     */
+    @Override
+    public int getMaxLoadedJsps() {
+        return maxLoadedJsps;
+    }
+
+    /**
+     * Should any jsps be unloaded when being idle for this time in seconds?
+     * If set to a value greater than 0 eviction of jsps is started. Default: -1
+     */
+    @Override
+    public int getJspIdleTimeout() {
+        return jspIdleTimeout;
+    }
+
+    /**
+     * Create an EmbeddedServletOptions object using data available from
+     * ServletConfig and ServletContext. 
+     */
+    public EmbeddedServletOptions(ServletConfig config,
+            ServletContext context) {
+        
+        Enumeration<String> enumeration=config.getInitParameterNames();
+        while( enumeration.hasMoreElements() ) {
+            String k=enumeration.nextElement();
+            String v=config.getInitParameter( k );
+            setProperty( k, v);
+        }
+        
+        // quick hack
+        String validating=config.getInitParameter( "validating");
+        if( "false".equals( validating )) ParserUtils.validating=false;
+        
+        String keepgen = config.getInitParameter("keepgenerated");
+        if (keepgen != null) {
+            if (keepgen.equalsIgnoreCase("true")) {
+                this.keepGenerated = true;
+            } else if (keepgen.equalsIgnoreCase("false")) {
+                this.keepGenerated = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.keepgen"));
+                }
+            }
+        }
+        
+        
+        String trimsp = config.getInitParameter("trimSpaces"); 
+        if (trimsp != null) {
+            if (trimsp.equalsIgnoreCase("true")) {
+                trimSpaces = true;
+            } else if (trimsp.equalsIgnoreCase("false")) {
+                trimSpaces = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.trimspaces"));
+                }
+            }
+        }
+        
+        this.isPoolingEnabled = true;
+        String poolingEnabledParam
+        = config.getInitParameter("enablePooling"); 
+        if (poolingEnabledParam != null
+                && !poolingEnabledParam.equalsIgnoreCase("true")) {
+            if (poolingEnabledParam.equalsIgnoreCase("false")) {
+                this.isPoolingEnabled = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.enablePooling"));
+                }
+            }
+        }
+        
+        String mapFile = config.getInitParameter("mappedfile"); 
+        if (mapFile != null) {
+            if (mapFile.equalsIgnoreCase("true")) {
+                this.mappedFile = true;
+            } else if (mapFile.equalsIgnoreCase("false")) {
+                this.mappedFile = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.mappedFile"));
+                }
+            }
+        }
+        
+        String debugInfo = config.getInitParameter("classdebuginfo");
+        if (debugInfo != null) {
+            if (debugInfo.equalsIgnoreCase("true")) {
+                this.classDebugInfo  = true;
+            } else if (debugInfo.equalsIgnoreCase("false")) {
+                this.classDebugInfo  = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.classDebugInfo"));
+                }
+            }
+        }
+        
+        String checkInterval = config.getInitParameter("checkInterval");
+        if (checkInterval != null) {
+            try {
+                this.checkInterval = Integer.parseInt(checkInterval);
+            } catch(NumberFormatException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.checkInterval"));
+                }
+            }
+        }
+        
+        String modificationTestInterval = config.getInitParameter("modificationTestInterval");
+        if (modificationTestInterval != null) {
+            try {
+                this.modificationTestInterval = Integer.parseInt(modificationTestInterval);
+            } catch(NumberFormatException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.modificationTestInterval"));
+                }
+            }
+        }
+        
+        String recompileOnFail = config.getInitParameter("recompileOnFail"); 
+        if (recompileOnFail != null) {
+            if (recompileOnFail.equalsIgnoreCase("true")) {
+                this.recompileOnFail = true;
+            } else if (recompileOnFail.equalsIgnoreCase("false")) {
+                this.recompileOnFail = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.recompileOnFail"));
+                }
+            }
+        }
+        String development = config.getInitParameter("development");
+        if (development != null) {
+            if (development.equalsIgnoreCase("true")) {
+                this.development = true;
+            } else if (development.equalsIgnoreCase("false")) {
+                this.development = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.development"));
+                }
+            }
+        }
+        
+        String suppressSmap = config.getInitParameter("suppressSmap");
+        if (suppressSmap != null) {
+            if (suppressSmap.equalsIgnoreCase("true")) {
+                isSmapSuppressed = true;
+            } else if (suppressSmap.equalsIgnoreCase("false")) {
+                isSmapSuppressed = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.suppressSmap"));
+                }
+            }
+        }
+        
+        String dumpSmap = config.getInitParameter("dumpSmap");
+        if (dumpSmap != null) {
+            if (dumpSmap.equalsIgnoreCase("true")) {
+                isSmapDumped = true;
+            } else if (dumpSmap.equalsIgnoreCase("false")) {
+                isSmapDumped = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.dumpSmap"));
+                }
+            }
+        }
+        
+        String genCharArray = config.getInitParameter("genStringAsCharArray");
+        if (genCharArray != null) {
+            if (genCharArray.equalsIgnoreCase("true")) {
+                genStringAsCharArray = true;
+            } else if (genCharArray.equalsIgnoreCase("false")) {
+                genStringAsCharArray = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.genchararray"));
+                }
+            }
+        }
+        
+        String errBeanClass =
+            config.getInitParameter("errorOnUseBeanInvalidClassAttribute");
+        if (errBeanClass != null) {
+            if (errBeanClass.equalsIgnoreCase("true")) {
+                errorOnUseBeanInvalidClassAttribute = true;
+            } else if (errBeanClass.equalsIgnoreCase("false")) {
+                errorOnUseBeanInvalidClassAttribute = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.errBean"));
+                }
+            }
+        }
+        
+        String ieClassId = config.getInitParameter("ieClassId");
+        if (ieClassId != null)
+            this.ieClassId = ieClassId;
+        
+        String classpath = config.getInitParameter("classpath");
+        if (classpath != null)
+            this.classpath = classpath;
+        
+        /*
+         * scratchdir
+         */
+        String dir = config.getInitParameter("scratchdir"); 
+        if (dir != null) {
+            scratchDir = new File(dir);
+        } else {
+            // First try the Servlet 2.2 javax.servlet.context.tempdir property
+            scratchDir = (File) context.getAttribute(ServletContext.TEMPDIR);
+            if (scratchDir == null) {
+                // Not running in a Servlet 2.2 container.
+                // Try to get the JDK 1.2 java.io.tmpdir property
+                dir = System.getProperty("java.io.tmpdir");
+                if (dir != null)
+                    scratchDir = new File(dir);
+            }
+        }      
+        if (this.scratchDir == null) {
+            log.fatal(Localizer.getMessage("jsp.error.no.scratch.dir"));
+            return;
+        }
+        
+        if (!(scratchDir.exists() && scratchDir.canRead() &&
+                scratchDir.canWrite() && scratchDir.isDirectory()))
+            log.fatal(Localizer.getMessage("jsp.error.bad.scratch.dir",
+                    scratchDir.getAbsolutePath()));
+        
+        this.compiler = config.getInitParameter("compiler");
+        
+        String compilerTargetVM = config.getInitParameter("compilerTargetVM");
+        if(compilerTargetVM != null) {
+            this.compilerTargetVM = compilerTargetVM;
+        }
+        
+        String compilerSourceVM = config.getInitParameter("compilerSourceVM");
+        if(compilerSourceVM != null) {
+            this.compilerSourceVM = compilerSourceVM;
+        }
+        
+        String javaEncoding = config.getInitParameter("javaEncoding");
+        if (javaEncoding != null) {
+            this.javaEncoding = javaEncoding;
+        }
+        
+        String compilerClassName = config.getInitParameter("compilerClassName");
+        if (compilerClassName != null) {
+            this.compilerClassName = compilerClassName;
+        }
+        
+        String fork = config.getInitParameter("fork");
+        if (fork != null) {
+            if (fork.equalsIgnoreCase("true")) {
+                this.fork = true;
+            } else if (fork.equalsIgnoreCase("false")) {
+                this.fork = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.fork"));
+                }
+            }
+        }
+        
+        String xpoweredBy = config.getInitParameter("xpoweredBy"); 
+        if (xpoweredBy != null) {
+            if (xpoweredBy.equalsIgnoreCase("true")) {
+                this.xpoweredBy = true;
+            } else if (xpoweredBy.equalsIgnoreCase("false")) {
+                this.xpoweredBy = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.xpoweredBy"));
+                }
+            }
+        }
+        
+        String displaySourceFragment = config.getInitParameter("displaySourceFragment"); 
+        if (displaySourceFragment != null) {
+            if (displaySourceFragment.equalsIgnoreCase("true")) {
+                this.displaySourceFragment = true;
+            } else if (displaySourceFragment.equalsIgnoreCase("false")) {
+                this.displaySourceFragment = false;
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.displaySourceFragment"));
+                }
+            }
+        }
+        
+        String maxLoadedJsps = config.getInitParameter("maxLoadedJsps");
+        if (maxLoadedJsps != null) {
+            try {
+                this.maxLoadedJsps = Integer.parseInt(maxLoadedJsps);
+            } catch(NumberFormatException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.maxLoadedJsps", ""+this.maxLoadedJsps));
+                }
+            }
+        }
+        
+        String jspIdleTimeout = config.getInitParameter("jspIdleTimeout");
+        if (jspIdleTimeout != null) {
+            try {
+                this.jspIdleTimeout = Integer.parseInt(jspIdleTimeout);
+            } catch(NumberFormatException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage("jsp.warning.jspIdleTimeout", ""+this.jspIdleTimeout));
+                }
+            }
+        }
+
+        // Setup the global Tag Libraries location cache for this
+        // web-application.
+        tldLocationsCache = new TldLocationsCache(context);
+        
+        // Setup the jsp config info for this web app.
+        jspConfig = new JspConfig(context);
+        
+        // Create a Tag plugin instance
+        tagPluginManager = new TagPluginManager(context);
+    }
+    
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/JasperException.java b/bundles/org.apache.tomcat/src/org/apache/jasper/JasperException.java
new file mode 100644
index 0000000..582c666
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/JasperException.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper;
+
+/**
+ * Base class for all exceptions generated by the JSP engine. Makes it
+ * convenient to catch just this at the top-level. 
+ *
+ * @author Anil K. Vijendran
+ */
+public class JasperException extends javax.servlet.ServletException {
+    
+    private static final long serialVersionUID = 1L;
+
+    public JasperException(String reason) {
+        super(reason);
+    }
+
+    /**
+     * Creates a JasperException with the embedded exception and the reason for
+     * throwing a JasperException
+     */
+    public JasperException (String reason, Throwable exception) {
+        super(reason, exception);
+    }
+
+    /**
+     * Creates a JasperException with the embedded exception
+     */
+    public JasperException (Throwable exception) {
+        super(exception);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/JspCompilationContext.java b/bundles/org.apache.tomcat/src/org/apache/jasper/JspCompilationContext.java
new file mode 100644
index 0000000..19a6116
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/JspCompilationContext.java
@@ -0,0 +1,753 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.tagext.TagInfo;
+
+import org.apache.jasper.compiler.Compiler;
+import org.apache.jasper.compiler.JarResource;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.compiler.JspUtil;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.compiler.ServletWriter;
+import org.apache.jasper.compiler.TldLocation;
+import org.apache.jasper.servlet.JasperLoader;
+import org.apache.jasper.servlet.JspServletWrapper;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * A place holder for various things that are used through out the JSP
+ * engine. This is a per-request/per-context data structure. Some of
+ * the instance variables are set at different points.
+ *
+ * Most of the path-related stuff is here - mangling names, versions, dirs,
+ * loading resources and dealing with uris. 
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Pierre Delisle
+ * @author Costin Manolache
+ * @author Kin-man Chung
+ */
+public class JspCompilationContext {
+
+    private final Log log = LogFactory.getLog(JspCompilationContext.class); // must not be static
+
+    protected Map<String, JarResource> tagFileJarUrls;
+    protected boolean isPackagedTagFile;
+
+    protected String className;
+    protected String jspUri;
+    protected boolean isErrPage;
+    protected String basePackageName;
+    protected String derivedPackageName;
+    protected String servletJavaFileName;
+    protected String javaPath;
+    protected String classFileName;
+    protected String contentType;
+    protected ServletWriter writer;
+    protected Options options;
+    protected JspServletWrapper jsw;
+    protected Compiler jspCompiler;
+    protected String classPath;
+
+    protected String baseURI;
+    protected String outputDir;
+    protected ServletContext context;
+    protected ClassLoader loader;
+
+    protected JspRuntimeContext rctxt;
+
+    protected volatile int removed = 0;
+
+    protected URLClassLoader jspLoader;
+    protected URL baseUrl;
+    protected Class<?> servletClass;
+
+    protected boolean isTagFile;
+    protected boolean protoTypeMode;
+    protected TagInfo tagInfo;
+    protected JarResource tagJarResource;
+
+    // jspURI _must_ be relative to the context
+    public JspCompilationContext(String jspUri,
+                                 boolean isErrPage,
+                                 Options options,
+                                 ServletContext context,
+                                 JspServletWrapper jsw,
+                                 JspRuntimeContext rctxt) {
+
+        this.jspUri = canonicalURI(jspUri);
+        this.isErrPage = isErrPage;
+        this.options = options;
+        this.jsw = jsw;
+        this.context = context;
+
+        this.baseURI = jspUri.substring(0, jspUri.lastIndexOf('/') + 1);
+        // hack fix for resolveRelativeURI
+        if (baseURI == null) {
+            baseURI = "/";
+        } else if (baseURI.charAt(0) != '/') {
+            // strip the base slash since it will be combined with the
+            // uriBase to generate a file
+            baseURI = "/" + baseURI;
+        }
+        if (baseURI.charAt(baseURI.length() - 1) != '/') {
+            baseURI += '/';
+        }
+
+        this.rctxt = rctxt;
+        this.tagFileJarUrls = new HashMap<String, JarResource>();
+        this.basePackageName = Constants.JSP_PACKAGE_NAME;
+    }
+
+    public JspCompilationContext(String tagfile,
+                                 TagInfo tagInfo, 
+                                 Options options,
+                                 ServletContext context,
+                                 JspServletWrapper jsw,
+                                 JspRuntimeContext rctxt,
+                                 JarResource tagJarResource) {
+        this(tagfile, false, options, context, jsw, rctxt);
+        this.isTagFile = true;
+        this.tagInfo = tagInfo;
+        this.tagJarResource = tagJarResource;
+        if (tagJarResource != null) {
+            isPackagedTagFile = true;
+        }
+    }
+
+    /* ==================== Methods to override ==================== */
+    
+    /** ---------- Class path and loader ---------- */
+
+    /**
+     * The classpath that is passed off to the Java compiler. 
+     */
+    public String getClassPath() {
+        if( classPath != null )
+            return classPath;
+        return rctxt.getClassPath();
+    }
+
+    /**
+     * The classpath that is passed off to the Java compiler. 
+     */
+    public void setClassPath(String classPath) {
+        this.classPath = classPath;
+    }
+
+    /**
+     * What class loader to use for loading classes while compiling
+     * this JSP?
+     */
+    public ClassLoader getClassLoader() {
+        if( loader != null )
+            return loader;
+        return rctxt.getParentClassLoader();
+    }
+
+    public void setClassLoader(ClassLoader loader) {
+        this.loader = loader;
+    }
+
+    public ClassLoader getJspLoader() {
+        if( jspLoader == null ) {
+            jspLoader = new JasperLoader
+            (new URL[] {baseUrl},
+                    getClassLoader(),
+                    rctxt.getPermissionCollection());
+        }
+        return jspLoader;
+    }
+
+    /** ---------- Input/Output  ---------- */
+    
+    /**
+     * The output directory to generate code into.  The output directory
+     * is make up of the scratch directory, which is provide in Options,
+     * plus the directory derived from the package name.
+     */
+    public String getOutputDir() {
+        if (outputDir == null) {
+            createOutputDir();
+        }
+
+        return outputDir;
+    }
+
+    /**
+     * Create a "Compiler" object based on some init param data. This
+     * is not done yet. Right now we're just hardcoding the actual
+     * compilers that are created. 
+     */
+    public Compiler createCompiler() {
+        if (jspCompiler != null ) {
+            return jspCompiler;
+        }
+        jspCompiler = null;
+        if (options.getCompilerClassName() != null) {
+            jspCompiler = createCompiler(options.getCompilerClassName());
+        } else {
+            if (options.getCompiler() == null) {
+                jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
+                if (jspCompiler == null) {
+                    jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
+                }
+            } else {
+                jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
+                if (jspCompiler == null) {
+                    jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
+                }
+            }
+        }
+        if (jspCompiler == null) {
+            throw new IllegalStateException(Localizer.getMessage("jsp.error.compiler"));
+        }
+        jspCompiler.init(this, jsw);
+        return jspCompiler;
+    }
+
+    protected Compiler createCompiler(String className) {
+        Compiler compiler = null; 
+        try {
+            compiler = (Compiler) Class.forName(className).newInstance();
+        } catch (InstantiationException e) {
+            log.warn(Localizer.getMessage("jsp.error.compiler"), e);
+        } catch (IllegalAccessException e) {
+            log.warn(Localizer.getMessage("jsp.error.compiler"), e);
+        } catch (NoClassDefFoundError e) {
+            if (log.isDebugEnabled()) {
+                log.debug(Localizer.getMessage("jsp.error.compiler"), e);
+            }
+        } catch (ClassNotFoundException e) {
+            if (log.isDebugEnabled()) {
+                log.debug(Localizer.getMessage("jsp.error.compiler"), e);
+            }
+        }
+        return compiler;
+    }
+    
+    public Compiler getCompiler() {
+        return jspCompiler;
+    }
+
+    /** ---------- Access resources in the webapp ---------- */
+
+    /** 
+     * Get the full value of a URI relative to this compilations context
+     * uses current file as the base.
+     */
+    public String resolveRelativeUri(String uri) {
+        // sometimes we get uri's massaged from File(String), so check for
+        // a root directory separator char
+        if (uri.startsWith("/") || uri.startsWith(File.separator)) {
+            return uri;
+        } else {
+            return baseURI + uri;
+        }
+    }
+
+    /**
+     * Gets a resource as a stream, relative to the meanings of this
+     * context's implementation.
+     * @return a null if the resource cannot be found or represented 
+     *         as an InputStream.
+     */
+    public java.io.InputStream getResourceAsStream(String res) {
+        return context.getResourceAsStream(canonicalURI(res));
+    }
+
+
+    public URL getResource(String res) throws MalformedURLException {
+        URL result = null;
+
+        if (res.startsWith("/META-INF/")) {
+            // This is a tag file packaged in a jar that is being compiled
+            JarResource jarResource = tagFileJarUrls.get(res);
+            if (jarResource == null) {
+                jarResource = tagJarResource;
+            }
+            if (jarResource != null) {
+                result = jarResource.getEntry(res.substring(1));
+            } else {
+                // May not be in a JAR in some IDE environments
+                result = context.getResource(canonicalURI(res));
+            }
+        } else if (res.startsWith("jar:file:")) {
+                // This is a tag file packaged in a jar that is being checked
+                // for a dependency
+                result = new URL(res);
+
+        } else {
+            result = context.getResource(canonicalURI(res));
+        }
+        return result;
+    }
+
+
+    public Set<String> getResourcePaths(String path) {
+        return context.getResourcePaths(canonicalURI(path));
+    }
+
+    /** 
+     * Gets the actual path of a URI relative to the context of
+     * the compilation.
+     */
+    public String getRealPath(String path) {
+        if (context != null) {
+            return context.getRealPath(path);
+        }
+        return path;
+    }
+
+    /**
+     * Returns the tag-file-name-to-JAR-file map of this compilation unit,
+     * which maps tag file names to the JAR files in which the tag files are
+     * packaged.
+     *
+     * The map is populated when parsing the tag-file elements of the TLDs
+     * of any imported taglibs. 
+     */
+    public JarResource getTagFileJarResource(String tagFile) {
+        return this.tagFileJarUrls.get(tagFile);
+    }
+
+    public void setTagFileJarResource(String tagFile, JarResource jarResource) {
+        this.tagFileJarUrls.put(tagFile, jarResource);
+    }
+
+    /**
+     * Returns the JAR file in which the tag file for which this
+     * JspCompilationContext was created is packaged, or null if this
+     * JspCompilationContext does not correspond to a tag file, or if the
+     * corresponding tag file is not packaged in a JAR.
+     */
+    public JarResource getTagFileJarResource() {
+        return this.tagJarResource;
+    }
+
+    /* ==================== Common implementation ==================== */
+
+    /**
+     * Just the class name (does not include package name) of the
+     * generated class. 
+     */
+    public String getServletClassName() {
+
+        if (className != null) {
+            return className;
+        }
+
+        if (isTagFile) {
+            className = tagInfo.getTagClassName();
+            int lastIndex = className.lastIndexOf('.');
+            if (lastIndex != -1) {
+                className = className.substring(lastIndex + 1);
+            }
+        } else {
+            int iSep = jspUri.lastIndexOf('/') + 1;
+            className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep));
+        }
+        return className;
+    }
+
+    public void setServletClassName(String className) {
+        this.className = className;
+    }
+    
+    /**
+     * Path of the JSP URI. Note that this is not a file name. This is
+     * the context rooted URI of the JSP file. 
+     */
+    public String getJspFile() {
+        return jspUri;
+    }
+
+    /**
+     * Are we processing something that has been declared as an
+     * errorpage? 
+     */
+    public boolean isErrorPage() {
+        return isErrPage;
+    }
+
+    public void setErrorPage(boolean isErrPage) {
+        this.isErrPage = isErrPage;
+    }
+
+    public boolean isTagFile() {
+        return isTagFile;
+    }
+
+    public TagInfo getTagInfo() {
+        return tagInfo;
+    }
+
+    public void setTagInfo(TagInfo tagi) {
+        tagInfo = tagi;
+    }
+
+    /**
+     * True if we are compiling a tag file in prototype mode.
+     * ie we only generate codes with class for the tag handler with empty
+     * method bodies.
+     */
+    public boolean isPrototypeMode() {
+        return protoTypeMode;
+    }
+
+    public void setPrototypeMode(boolean pm) {
+        protoTypeMode = pm;
+    }
+
+    /**
+     * Package name for the generated class is make up of the base package
+     * name, which is user settable, and the derived package name.  The
+     * derived package name directly mirrors the file hierarchy of the JSP page.
+     */
+    public String getServletPackageName() {
+        if (isTagFile()) {
+            String className = tagInfo.getTagClassName();
+            int lastIndex = className.lastIndexOf('.');
+            String pkgName = "";
+            if (lastIndex != -1) {
+                pkgName = className.substring(0, lastIndex);
+            }
+            return pkgName;
+        } else {
+            String dPackageName = getDerivedPackageName();
+            if (dPackageName.length() == 0) {
+                return basePackageName;
+            }
+            return basePackageName + '.' + getDerivedPackageName();
+        }
+    }
+
+    protected String getDerivedPackageName() {
+        if (derivedPackageName == null) {
+            int iSep = jspUri.lastIndexOf('/');
+            derivedPackageName = (iSep > 0) ?
+                    JspUtil.makeJavaPackage(jspUri.substring(1,iSep)) : "";
+        }
+        return derivedPackageName;
+    }
+            
+    /**
+     * The package name into which the servlet class is generated.
+     */
+    public void setServletPackageName(String servletPackageName) {
+        this.basePackageName = servletPackageName;
+    }
+
+    /**
+     * Full path name of the Java file into which the servlet is being
+     * generated. 
+     */
+    public String getServletJavaFileName() {
+        if (servletJavaFileName == null) {
+            servletJavaFileName = getOutputDir() + getServletClassName() + ".java";
+        }
+        return servletJavaFileName;
+    }
+
+    /**
+     * Get hold of the Options object for this context. 
+     */
+    public Options getOptions() {
+        return options;
+    }
+
+    public ServletContext getServletContext() {
+        return context;
+    }
+
+    public JspRuntimeContext getRuntimeContext() {
+        return rctxt;
+    }
+
+    /**
+     * Path of the Java file relative to the work directory.
+     */
+    public String getJavaPath() {
+
+        if (javaPath != null) {
+            return javaPath;
+        }
+
+        if (isTagFile()) {
+            String tagName = tagInfo.getTagClassName();
+            javaPath = tagName.replace('.', '/') + ".java";
+        } else {
+            javaPath = getServletPackageName().replace('.', '/') + '/' +
+                       getServletClassName() + ".java";
+        }
+        return javaPath;
+    }
+
+    public String getClassFileName() {
+        if (classFileName == null) {
+            classFileName = getOutputDir() + getServletClassName() + ".class";
+        }
+        return classFileName;
+    }
+
+    /**
+     * Get the content type of this JSP.
+     *
+     * Content type includes content type and encoding.
+     */
+    public String getContentType() {
+        return contentType;
+    }
+
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    /**
+     * Where is the servlet being generated?
+     */
+    public ServletWriter getWriter() {
+        return writer;
+    }
+
+    public void setWriter(ServletWriter writer) {
+        this.writer = writer;
+    }
+
+    /**
+     * Gets the 'location' of the TLD associated with the given taglib 'uri'.
+     * 
+     * @return An array of two Strings: The first element denotes the real
+     * path to the TLD. If the path to the TLD points to a jar file, then the
+     * second element denotes the name of the TLD entry in the jar file.
+     * Returns null if the given uri is not associated with any tag library
+     * 'exposed' in the web application.
+     */
+    public TldLocation getTldLocation(String uri) throws JasperException {
+        TldLocation location = 
+            getOptions().getTldLocationsCache().getLocation(uri);
+        return location;
+    }
+
+    /**
+     * Are we keeping generated code around?
+     */
+    public boolean keepGenerated() {
+        return getOptions().getKeepGenerated();
+    }
+
+    // ==================== Removal ==================== 
+
+    public void incrementRemoved() {
+        if (removed == 0 && rctxt != null) {
+            rctxt.removeWrapper(jspUri);
+        }
+        removed++;
+    }
+
+    public boolean isRemoved() {
+        if (removed > 0 ) {
+            return true;
+        }
+        return false;
+    }
+
+    // ==================== Compile and reload ====================
+    
+    public void compile() throws JasperException, FileNotFoundException {
+        createCompiler();
+        if (jspCompiler.isOutDated()) {
+            if (isRemoved()) {
+                throw new FileNotFoundException(jspUri);
+            }
+            try {
+                jspCompiler.removeGeneratedFiles();
+                jspLoader = null;
+                jspCompiler.compile();
+                jsw.setReload(true);
+                jsw.setCompilationException(null);
+            } catch (JasperException ex) {
+                // Cache compilation exception
+                jsw.setCompilationException(ex);
+                if (options.getDevelopment() && options.getRecompileOnFail()) {
+                    // Force a recompilation attempt on next access
+                    jsw.setLastModificationTest(-1);
+                }
+                throw ex;
+            } catch (Exception ex) {
+                JasperException je = new JasperException(
+                            Localizer.getMessage("jsp.error.unable.compile"),
+                            ex);
+                // Cache compilation exception
+                jsw.setCompilationException(je);
+                throw je;
+            }
+        }
+    }
+
+    // ==================== Manipulating the class ====================
+
+    public Class<?> load() throws JasperException {
+        try {
+            getJspLoader();
+            
+            String name = getFQCN();
+            servletClass = jspLoader.loadClass(name);
+        } catch (ClassNotFoundException cex) {
+            throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
+                                      cex);
+        } catch (Exception ex) {
+            throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
+                                      ex);
+        }
+        removed = 0;
+        return servletClass;
+    }
+
+    public String getFQCN() {
+        String name;
+        if (isTagFile()) {
+            name = tagInfo.getTagClassName();
+        } else {
+            name = getServletPackageName() + "." + getServletClassName();
+        }
+        return name;
+    }
+
+    // ==================== protected methods ==================== 
+
+    static Object outputDirLock = new Object();
+
+    public void checkOutputDir() {
+        if (outputDir != null) {
+            if (!(new File(outputDir)).exists()) {
+                makeOutputDir();
+            }
+        } else {
+            createOutputDir();
+        }
+    }
+        
+    protected boolean makeOutputDir() {
+        synchronized(outputDirLock) {
+            File outDirFile = new File(outputDir);
+            return (outDirFile.exists() || outDirFile.mkdirs());
+        }
+    }
+
+    protected void createOutputDir() {
+        String path = null;
+        if (isTagFile()) {
+            String tagName = tagInfo.getTagClassName();
+            path = tagName.replace('.', File.separatorChar);
+            path = path.substring(0, path.lastIndexOf(File.separatorChar));
+        } else {
+            path = getServletPackageName().replace('.',File.separatorChar);
+        }
+
+            // Append servlet or tag handler path to scratch dir
+            try {
+                File base = options.getScratchDir();
+                baseUrl = base.toURI().toURL();
+                outputDir = base.getAbsolutePath() + File.separator + path + 
+                    File.separator;
+                if (!makeOutputDir()) {
+                    throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"));
+                }
+            } catch (MalformedURLException e) {
+                throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e);
+            }
+    }
+    
+    protected static final boolean isPathSeparator(char c) {
+       return (c == '/' || c == '\\');
+    }
+
+    protected static final String canonicalURI(String s) {
+       if (s == null) return null;
+       StringBuilder result = new StringBuilder();
+       final int len = s.length();
+       int pos = 0;
+       while (pos < len) {
+           char c = s.charAt(pos);
+           if ( isPathSeparator(c) ) {
+               /*
+                * multiple path separators.
+                * 'foo///bar' -> 'foo/bar'
+                */
+               while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) {
+                   ++pos;
+               }
+
+               if (pos+1 < len && s.charAt(pos+1) == '.') {
+                   /*
+                    * a single dot at the end of the path - we are done.
+                    */
+                   if (pos+2 >= len) break;
+
+                   switch (s.charAt(pos+2)) {
+                       /*
+                        * self directory in path
+                        * foo/./bar -> foo/bar
+                        */
+                   case '/':
+                   case '\\':
+                       pos += 2;
+                       continue;
+
+                       /*
+                        * two dots in a path: go back one hierarchy.
+                        * foo/bar/../baz -> foo/baz
+                        */
+                   case '.':
+                       // only if we have exactly _two_ dots.
+                       if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) {
+                           pos += 3;
+                           int separatorPos = result.length()-1;
+                           while (separatorPos >= 0 && 
+                                  ! isPathSeparator(result
+                                                    .charAt(separatorPos))) {
+                               --separatorPos;
+                           }
+                           if (separatorPos >= 0)
+                               result.setLength(separatorPos);
+                           continue;
+                       }
+                   }
+               }
+           }
+           result.append(c);
+           ++pos;
+       }
+       return result.toString();
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/Options.java b/bundles/org.apache.tomcat/src/org/apache/jasper/Options.java
new file mode 100644
index 0000000..55c0a00
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/Options.java
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper;
+
+import java.io.File;
+import java.util.Map;
+
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+import org.apache.jasper.compiler.JspConfig;
+import org.apache.jasper.compiler.TagPluginManager;
+import org.apache.jasper.compiler.TldLocationsCache;
+
+/**
+ * A class to hold all init parameters specific to the JSP engine. 
+ *
+ * @author Anil K. Vijendran
+ * @author Hans Bergsten
+ * @author Pierre Delisle
+ */
+public interface Options {
+
+    /**
+     * Returns true if Jasper issues a compilation error instead of a runtime
+     * Instantiation error if the class attribute specified in useBean action
+     * is invalid.
+     */
+    public boolean getErrorOnUseBeanInvalidClassAttribute();
+
+    /**
+     * Are we keeping generated code around?
+     */
+    public boolean getKeepGenerated();
+
+    /**
+     * Returns true if tag handler pooling is enabled, false otherwise.
+     */
+    public boolean isPoolingEnabled();
+
+    /**
+     * Are we supporting HTML mapped servlets?
+     */
+    public boolean getMappedFile();
+
+    /**
+     * Should we include debug information in compiled class?
+     */
+    public boolean getClassDebugInfo();
+
+    /**
+     * Background compile thread check interval in seconds
+     */
+    public int getCheckInterval();
+
+    /**
+     * Is Jasper being used in development mode?
+     */
+    public boolean getDevelopment();
+
+    /**
+     * Should we include a source fragment in exception messages, which could be displayed
+     * to the developer ?
+     */
+    public boolean getDisplaySourceFragment();
+
+    /**
+     * Is the generation of SMAP info for JSR45 debugging suppressed?
+     */
+    public boolean isSmapSuppressed();
+
+    /**
+     * Indicates whether SMAP info for JSR45 debugging should be dumped to a
+     * file.
+     * Ignored if suppressSmap() is true.
+     */
+    public boolean isSmapDumped();
+
+    /**
+     * Should white spaces between directives or actions be trimmed?
+     */
+    public boolean getTrimSpaces();
+
+    /**
+     * Gets the class-id value that is sent to Internet Explorer when using
+     * &lt;jsp:plugin&gt; tags.
+     *
+     * @return Class-id value
+     */
+    public String getIeClassId();
+
+    /**
+     * What is my scratch dir?
+     */
+    public File getScratchDir();
+
+    /**
+     * What classpath should I use while compiling the servlets
+     * generated from JSP files?
+     */
+    public String getClassPath();
+
+    /**
+     * Compiler to use.
+     * 
+     * <p>
+     * If <code>null</code> (the default), the java compiler from Eclipse JDT
+     * project, bundled with Tomcat, will be used. Otherwise, the
+     * <code>javac</code> task from Apache Ant will be used to call an external
+     * java compiler and the value of this option will be passed to it. See
+     * Apache Ant documentation for the possible values.
+     */
+    public String getCompiler();
+
+    /**
+     * The compiler target VM, e.g. 1.1, 1.2, 1.3, 1.4, 1.5 or 1.6.
+     */
+    public String getCompilerTargetVM();
+
+    /**
+     * The compiler source VM, e.g. 1.3, 1.4, 1.5 or 1.6.
+     */
+    public String getCompilerSourceVM();   
+
+    /**
+     * Jasper Java compiler class to use.
+     */
+    public String getCompilerClassName();   
+
+    /**
+     * The cache for the location of the TLD's
+     * for the various tag libraries 'exposed'
+     * by the web application.
+     * A tag library is 'exposed' either explicitly in 
+     * web.xml or implicitly via the uri tag in the TLD 
+     * of a taglib deployed in a jar file (WEB-INF/lib).
+     *
+     * @return the instance of the TldLocationsCache
+     * for the web-application.
+     */
+    public TldLocationsCache getTldLocationsCache();
+
+    /**
+     * Java platform encoding to generate the JSP
+     * page servlet.
+     */
+    public String getJavaEncoding();
+
+    /**
+     * The boolean flag to tell Ant whether to fork JSP page compilations.
+     * 
+     * <p>
+     * Is used only when Jasper uses an external java compiler (wrapped through
+     * a <code>javac</code> Apache Ant task).
+     */
+    public boolean getFork();
+
+    /**
+     * Obtain JSP configuration information specified in web.xml.  
+     */
+    public JspConfig getJspConfig();
+
+    /**
+     * Is generation of X-Powered-By response header enabled/disabled?
+     */
+    public boolean isXpoweredBy();
+
+    /**
+     * Obtain a Tag Plugin Manager
+     */
+    public TagPluginManager getTagPluginManager();
+
+    /**
+     * Indicates whether text strings are to be generated as char arrays.
+     * 
+     * @return <code>true</code> if text strings are to be generated as char
+     *         arrays, <code>false</code> otherwise
+     */
+    public boolean genStringAsCharArray();
+    
+    /**
+     * Modification test interval.
+     */
+    public int getModificationTestInterval();
+    
+
+    /**
+     * Re-compile on failure.
+     */
+    public boolean getRecompileOnFail();
+    
+    /**
+     * Is caching enabled (used for precompilation).
+     */
+    public boolean isCaching();
+    
+    /**
+     * The web-application wide cache for the TagLibraryInfo tag library
+     * descriptors, used if {@link #isCaching()} returns <code>true</code>.
+     * 
+     * <p>
+     * Using this cache avoids the cost of repeating the parsing of a tag
+     * library descriptor XML file (performed by TagLibraryInfoImpl.parseTLD).
+     * </p>
+     * 
+     * @return the Map(String uri, TagLibraryInfo tld) instance.
+     */
+    public Map<String, TagLibraryInfo> getCache();
+    
+    /**
+     * The maximum number of loaded jsps per web-application. If there are more
+     * jsps loaded, they will be unloaded. If unset or less than 0, no jsps
+     * are unloaded.
+     */
+    public int getMaxLoadedJsps();
+    
+    /**
+     * The idle time in seconds after which a JSP is unloaded.
+     * If unset or less or equal than 0, no jsps are unloaded.
+     */
+    public int getJspIdleTimeout();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/tagplugin/TagPlugin.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/tagplugin/TagPlugin.java
new file mode 100644
index 0000000..49cbc7c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/tagplugin/TagPlugin.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler.tagplugin;
+
+/**
+ * This interface is to be implemented by the plugin author, to supply
+ * an alternate implementation of the tag handlers.  It can be used to
+ * specify the Java codes to be generated when a tag is invoked.
+ *
+ * An implementation of this interface must be registered in a file
+ * named "tagPlugins.xml" under WEB-INF.
+ */
+
+public interface TagPlugin {
+
+    /**
+     * Generate codes for a custom tag.
+     * @param ctxt a TagPluginContext for accessing Jasper functions
+     */
+    void doTag(TagPluginContext ctxt);
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/tagplugin/TagPluginContext.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/tagplugin/TagPluginContext.java
new file mode 100644
index 0000000..f9d1db5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/tagplugin/TagPluginContext.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler.tagplugin;
+
+
+/**
+ * This interface allows the plugin author to make inquiries about the
+ * properties of the current tag, and to use Jasper resources to generate
+ * direct Java codes in place of tag handler invocations.
+ */
+
+public interface TagPluginContext {
+    /**
+     * @return true if the body of the tag is scriptless.
+     */
+    boolean isScriptless();
+
+    /**
+     * @param attribute Name of the attribute
+     * @return true if the attribute is specified in the tag
+     */
+    boolean isAttributeSpecified(String attribute);
+
+    /**
+     * @return An unique temporary variable name that the plugin can use.
+     */
+    String getTemporaryVariableName();
+
+    /**
+     * Generate an import statement
+     * @param s Name of the import class, '*' allowed.
+     */
+    void generateImport(String s);
+
+    /**
+     * Generate a declaration in the of the generated class.  This can be
+     * used to declare an inner class, a method, or a class variable.
+     * @param id An unique ID identifying the declaration.  It is not
+     *           part of the declaration, and is used to ensure that the
+     *           declaration will only appear once.  If this method is
+     *           invoked with the same id more than once in the translation
+     *           unit, only the first declaration will be taken.
+     * @param text The text of the declaration.
+     **/
+    void generateDeclaration(String id, String text);
+
+    /**
+     * Generate Java source codes
+     */
+    void generateJavaSource(String s);
+
+    /**
+     * @return true if the attribute is specified and its value is a
+     *         translation-time constant.
+     */
+    boolean isConstantAttribute(String attribute);
+
+    /**
+     * @return A string that is the value of a constant attribute.  Undefined
+     *         if the attribute is not a (translation-time) constant.
+     *         null if the attribute is not specified.
+     */
+    String getConstantAttribute(String attribute);
+
+    /**
+     * Generate codes to evaluate value of a attribute in the custom tag
+     * The codes is a Java expression.
+     * NOTE: Currently cannot handle attributes that are fragments.
+     * @param attribute The specified attribute
+     */
+    void generateAttribute(String attribute);
+
+    /**
+     * Generate codes for the body of the custom tag
+     */
+    void generateBody();
+
+    /**
+     * Abandon optimization for this tag handler, and instruct
+     * Jasper to generate the tag handler calls, as usual.
+     * Should be invoked if errors are detected, or when the tag body
+     * is deemed too complicated for optimization.
+     */
+    void dontUseTagPlugin();
+
+    /**
+     * Get the PluginContext for the parent of this custom tag.  NOTE:
+     * The operations available for PluginContext so obtained is limited
+     * to getPluginAttribute and setPluginAttribute, and queries (e.g.
+     * isScriptless().  There should be no calls to generate*().
+     * @return The pluginContext for the parent node.
+     *         null if the parent is not a custom tag, or if the pluginConxt
+     *         if not available (because useTagPlugin is false, e.g).
+     */
+    TagPluginContext getParentContext();
+
+    /**
+     * Associate the attribute with a value in the current tagplugin context.
+     * The plugin attributes can be used for communication among tags that
+     * must work together as a group.  See <c:when> for an example.
+     */
+    void setPluginAttribute(String attr, Object value);
+
+    /**
+     * Get the value of an attribute in the current tagplugin context.
+     */
+    Object getPluginAttribute(String attr);
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELContextImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELContextImpl.java
new file mode 100644
index 0000000..4739c6a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELContextImpl.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+/**
+ * Implementation of ELContext
+ * 
+ * @author Jacob Hookom
+ */
+public final class ELContextImpl extends ELContext {
+
+    private static final FunctionMapper NullFunctionMapper = new FunctionMapper() {
+        @Override
+        public Method resolveFunction(String prefix, String localName) {
+            return null;
+        }
+    };
+
+    private static final class VariableMapperImpl extends VariableMapper {
+
+        private Map<String, ValueExpression> vars;
+
+        @Override
+        public ValueExpression resolveVariable(String variable) {
+            if (vars == null) {
+                return null;
+            }
+            return vars.get(variable);
+        }
+
+        @Override
+        public ValueExpression setVariable(String variable,
+                ValueExpression expression) {
+            if (vars == null)
+                vars = new HashMap<String, ValueExpression>();
+            return vars.put(variable, expression);
+        }
+
+    }
+
+    private final ELResolver resolver;
+
+    private FunctionMapper functionMapper = NullFunctionMapper;
+
+    private VariableMapper variableMapper;
+
+    public ELContextImpl() {
+        this(ELResolverImpl.getDefaultResolver());
+    }
+
+    public ELContextImpl(ELResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    @Override
+    public ELResolver getELResolver() {
+        return this.resolver;
+    }
+
+    @Override
+    public FunctionMapper getFunctionMapper() {
+        return this.functionMapper;
+    }
+
+    @Override
+    public VariableMapper getVariableMapper() {
+        if (this.variableMapper == null) {
+            this.variableMapper = new VariableMapperImpl();
+        }
+        return this.variableMapper;
+    }
+
+    public void setFunctionMapper(FunctionMapper functionMapper) {
+        this.functionMapper = functionMapper;
+    }
+
+    public void setVariableMapper(VariableMapper variableMapper) {
+        this.variableMapper = variableMapper;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELContextWrapper.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELContextWrapper.java
new file mode 100644
index 0000000..bf8fca3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELContextWrapper.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import java.util.Locale;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.VariableMapper;
+
+/**
+ * Simple ELContextWrapper for runtime evaluation of EL w/ dynamic FunctionMappers
+ * 
+ * @author jhook
+ */
+public final class ELContextWrapper extends ELContext {
+
+    private final ELContext target;
+    private final FunctionMapper fnMapper;
+    
+    public ELContextWrapper(ELContext target, FunctionMapper fnMapper) {
+        this.target = target;
+        this.fnMapper = fnMapper;
+    }
+
+    @Override
+    public ELResolver getELResolver() {
+        return this.target.getELResolver();
+    }
+
+    @Override
+    public FunctionMapper getFunctionMapper() {
+        if (this.fnMapper != null) return this.fnMapper;
+        return this.target.getFunctionMapper();
+    }
+
+    @Override
+    public VariableMapper getVariableMapper() {
+        return this.target.getVariableMapper();
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes") // Can't use Class<?> because API needs to match super-class specification
+    public Object getContext(Class key) {
+        return this.target.getContext(key);
+    }
+
+    @Override
+    public Locale getLocale() {
+        return this.target.getLocale();
+    }
+
+    @Override
+    public boolean isPropertyResolved() {
+        return this.target.isPropertyResolved();
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes") // Can't use Class<?> because API needs to match super-class specification
+    public void putContext(Class key, Object contextObject) throws NullPointerException {
+        this.target.putContext(key, contextObject);
+    }
+
+    @Override
+    public void setLocale(Locale locale) {
+        this.target.setLocale(locale);
+    }
+
+    @Override
+    public void setPropertyResolved(boolean resolved) {
+        this.target.setPropertyResolved(resolved);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELResolverImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELResolverImpl.java
new file mode 100644
index 0000000..318e806
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ELResolverImpl.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.el;
+
+import java.util.Iterator;
+
+import javax.el.ArrayELResolver;
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ELResolver;
+import javax.el.ListELResolver;
+import javax.el.MapELResolver;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.el.ResourceBundleELResolver;
+import javax.servlet.jsp.el.VariableResolver;
+
+import org.apache.jasper.Constants;
+
+public final class ELResolverImpl extends ELResolver {
+    private static final ELResolver DefaultResolver;
+
+    static {
+        if (Constants.IS_SECURITY_ENABLED) {
+            DefaultResolver = null;
+        } else {
+            DefaultResolver = new CompositeELResolver();
+            ((CompositeELResolver) DefaultResolver).add(new MapELResolver());
+            ((CompositeELResolver) DefaultResolver).add(new ResourceBundleELResolver());
+            ((CompositeELResolver) DefaultResolver).add(new ListELResolver());
+            ((CompositeELResolver) DefaultResolver).add(new ArrayELResolver());
+            ((CompositeELResolver) DefaultResolver).add(new BeanELResolver());
+        }
+    }
+
+    private final VariableResolver variableResolver;
+    private final ELResolver elResolver;
+
+    public ELResolverImpl(VariableResolver variableResolver) {
+        this.variableResolver = variableResolver;
+        this.elResolver = getDefaultResolver();
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+            if (property != null) {
+                try {
+                    return this.variableResolver.resolveVariable(property
+                            .toString());
+                } catch (javax.servlet.jsp.el.ELException e) {
+                    throw new ELException(e.getMessage(), e.getCause());
+                }
+            }
+        }
+
+        if (!context.isPropertyResolved()) {
+            return elResolver.getValue(context, base, property);
+        }
+        return null;
+    }
+
+    @Override
+    public Class<?> getType(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+            if (property != null) {
+                try {
+                    Object obj = this.variableResolver.resolveVariable(property
+                            .toString());
+                    return (obj != null) ? obj.getClass() : null;
+                } catch (javax.servlet.jsp.el.ELException e) {
+                    throw new ELException(e.getMessage(), e.getCause());
+                }
+            }
+        }
+
+        if (!context.isPropertyResolved()) {
+            return elResolver.getType(context, base, property);
+        }
+        return null;
+    }
+
+    @Override
+    public void setValue(ELContext context, Object base, Object property,
+            Object value) throws NullPointerException,
+            PropertyNotFoundException, PropertyNotWritableException,
+            ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+            throw new PropertyNotWritableException(
+                    "Legacy VariableResolver wrapped, not writable");
+        }
+
+        if (!context.isPropertyResolved()) {
+            elResolver.setValue(context, base, property, value);
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            context.setPropertyResolved(true);
+            return true;
+        }
+
+        return elResolver.isReadOnly(context, base, property);
+    }
+
+    @Override
+    public Iterator<java.beans.FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+        return elResolver.getFeatureDescriptors(context, base);
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        if (base == null) {
+            return String.class;
+        }
+        return elResolver.getCommonPropertyType(context, base);
+    }
+
+    public static ELResolver getDefaultResolver() {
+        if (Constants.IS_SECURITY_ENABLED) {
+            CompositeELResolver defaultResolver = new CompositeELResolver();
+            defaultResolver.add(new MapELResolver());
+            defaultResolver.add(new ResourceBundleELResolver());
+            defaultResolver.add(new ListELResolver());
+            defaultResolver.add(new ArrayELResolver());
+            defaultResolver.add(new BeanELResolver());
+            return defaultResolver;
+        } else {
+            return DefaultResolver;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/ExpressionEvaluatorImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ExpressionEvaluatorImpl.java
new file mode 100644
index 0000000..b27dc7e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ExpressionEvaluatorImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ELParseException;
+import javax.servlet.jsp.el.Expression;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.FunctionMapper;
+import javax.servlet.jsp.el.VariableResolver;
+
+@Deprecated
+public final class ExpressionEvaluatorImpl extends ExpressionEvaluator {
+
+    private final ExpressionFactory factory;
+    
+    public ExpressionEvaluatorImpl(ExpressionFactory factory) {
+        this.factory = factory;
+    }
+
+    @Override
+    public Expression parseExpression(String expression,
+            @SuppressWarnings("rawtypes") // API does not use generics
+            Class expectedType,
+            FunctionMapper fMapper) throws ELException {
+        try {
+            ELContextImpl ctx =
+                new ELContextImpl(ELResolverImpl.getDefaultResolver());
+            if (fMapper != null) {
+                ctx.setFunctionMapper(new FunctionMapperImpl(fMapper));
+            }
+            ValueExpression ve = this.factory.createValueExpression(ctx, expression, expectedType);
+            return new ExpressionImpl(ve);
+        } catch (javax.el.ELException e) {
+            throw new ELParseException(e.getMessage());
+        }
+    }
+
+    @Override
+    public Object evaluate(String expression,
+            @SuppressWarnings("rawtypes") // API does not use generics
+            Class expectedType,
+            VariableResolver vResolver, FunctionMapper fMapper)
+            throws ELException {
+        return this.parseExpression(expression, expectedType, fMapper).evaluate(vResolver);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/ExpressionImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ExpressionImpl.java
new file mode 100644
index 0000000..69f2d5e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/ExpressionImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import javax.el.ELContext;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.Expression;
+import javax.servlet.jsp.el.VariableResolver;
+
+@Deprecated
+public final class ExpressionImpl extends Expression {
+
+    private final ValueExpression ve;
+    
+    public ExpressionImpl(ValueExpression ve) {
+        this.ve = ve;
+    }
+
+    @Override
+    public Object evaluate(VariableResolver vResolver) throws ELException {
+        ELContext ctx = new ELContextImpl(new ELResolverImpl(vResolver));
+        return ve.getValue(ctx);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/FunctionMapperImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/FunctionMapperImpl.java
new file mode 100644
index 0000000..d273c44
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/FunctionMapperImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import java.lang.reflect.Method;
+
+import javax.servlet.jsp.el.FunctionMapper;
+
+@Deprecated
+public final class FunctionMapperImpl extends javax.el.FunctionMapper {
+    
+    private final FunctionMapper fnMapper;
+
+    public FunctionMapperImpl(FunctionMapper fnMapper) {
+        this.fnMapper = fnMapper;
+    }
+
+    @Override
+    public Method resolveFunction(String prefix, String localName) {
+        return this.fnMapper.resolveFunction(prefix, localName);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspELException.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspELException.java
new file mode 100644
index 0000000..3651a65
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspELException.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import javax.el.ELException;
+
+public class JspELException extends ELException {
+
+    private static final long serialVersionUID = 1L;
+
+    public JspELException(String mark, ELException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspMethodExpression.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspMethodExpression.java
new file mode 100644
index 0000000..51d4c55
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspMethodExpression.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.MethodExpression;
+import javax.el.MethodInfo;
+import javax.el.MethodNotFoundException;
+import javax.el.PropertyNotFoundException;
+
+public final class JspMethodExpression extends MethodExpression implements
+        Externalizable {
+
+    private String mark;
+
+    private MethodExpression target;
+
+    public JspMethodExpression() {
+        super();
+    }
+
+    public JspMethodExpression(String mark, MethodExpression target) {
+        this.target = target;
+        this.mark = mark;
+    }
+
+    @Override
+    public MethodInfo getMethodInfo(ELContext context)
+            throws NullPointerException, PropertyNotFoundException,
+            MethodNotFoundException, ELException {
+        try {
+            return this.target.getMethodInfo(context);
+        } catch (MethodNotFoundException e) {
+            if (e instanceof JspMethodNotFoundException) throw e;
+            throw new JspMethodNotFoundException(this.mark, e);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    @Override
+    public Object invoke(ELContext context, Object[] params)
+            throws NullPointerException, PropertyNotFoundException,
+            MethodNotFoundException, ELException {
+        try {
+            return this.target.invoke(context, params);
+        } catch (MethodNotFoundException e) {
+            if (e instanceof JspMethodNotFoundException) throw e;
+            throw new JspMethodNotFoundException(this.mark, e);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return this.target.equals(obj);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.target.hashCode();
+    }
+
+    @Override
+    public String getExpressionString() {
+        return this.target.getExpressionString();
+    }
+
+    @Override
+    public boolean isLiteralText() {
+        return this.target.isLiteralText();
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.mark);
+        out.writeObject(this.target);
+    }
+
+    @Override
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.mark = in.readUTF();
+        this.target = (MethodExpression) in.readObject();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspMethodNotFoundException.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspMethodNotFoundException.java
new file mode 100644
index 0000000..249576d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspMethodNotFoundException.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import javax.el.MethodNotFoundException;
+
+public class JspMethodNotFoundException extends MethodNotFoundException {
+
+    private static final long serialVersionUID = 1L;
+
+    public JspMethodNotFoundException(String mark, MethodNotFoundException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspPropertyNotFoundException.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspPropertyNotFoundException.java
new file mode 100644
index 0000000..e09b763
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspPropertyNotFoundException.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import javax.el.PropertyNotFoundException;
+
+public final class JspPropertyNotFoundException extends
+        PropertyNotFoundException {
+
+    private static final long serialVersionUID = 1L;
+
+    public JspPropertyNotFoundException(String mark, PropertyNotFoundException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspPropertyNotWritableException.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspPropertyNotWritableException.java
new file mode 100644
index 0000000..9510777
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspPropertyNotWritableException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import javax.el.PropertyNotWritableException;
+
+public class JspPropertyNotWritableException extends
+        PropertyNotWritableException {
+
+    private static final long serialVersionUID = 1L;
+
+    public JspPropertyNotWritableException(String mark, PropertyNotWritableException e) {
+        super(mark + " " + e.getMessage(), e.getCause());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspValueExpression.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspValueExpression.java
new file mode 100644
index 0000000..cc5a410
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/JspValueExpression.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
+import javax.el.ValueExpression;
+
+/**
+ * Wrapper for providing context to ValueExpressions
+ * 
+ * @author Jacob Hookom
+ */
+public final class JspValueExpression extends ValueExpression implements
+        Externalizable {
+
+    private ValueExpression target;
+
+    private String mark;
+
+    public JspValueExpression() {
+        super();
+    }
+
+    public JspValueExpression(String mark, ValueExpression target) {
+        this.target = target;
+        this.mark = mark;
+    }
+
+    @Override
+    public Class<?> getExpectedType() {
+        return this.target.getExpectedType();
+    }
+
+    @Override
+    public Class<?> getType(ELContext context) throws NullPointerException,
+            PropertyNotFoundException, ELException {
+        try {
+            return this.target.getType(context);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context) throws NullPointerException,
+            PropertyNotFoundException, ELException {
+        try {
+            return this.target.isReadOnly(context);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    @Override
+    public void setValue(ELContext context, Object value)
+            throws NullPointerException, PropertyNotFoundException,
+            PropertyNotWritableException, ELException {
+        try {
+            this.target.setValue(context, value);
+        } catch (PropertyNotWritableException e) {
+            if (e instanceof JspPropertyNotWritableException) throw e;
+            throw new JspPropertyNotWritableException(this.mark, e);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    @Override
+    public Object getValue(ELContext context) throws NullPointerException,
+            PropertyNotFoundException, ELException {
+        try {
+            return this.target.getValue(context);
+        } catch (PropertyNotFoundException e) {
+            if (e instanceof JspPropertyNotFoundException) throw e;
+            throw new JspPropertyNotFoundException(this.mark, e);
+        } catch (ELException e) {
+            if (e instanceof JspELException) throw e;
+            throw new JspELException(this.mark, e);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return this.target.equals(obj);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.target.hashCode();
+    }
+
+    @Override
+    public String getExpressionString() {
+        return this.target.getExpressionString();
+    }
+
+    @Override
+    public boolean isLiteralText() {
+        return this.target.isLiteralText();
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.mark);
+        out.writeObject(this.target);
+    }
+
+    @Override
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.mark = in.readUTF();
+        this.target = (ValueExpression) in.readObject();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/el/VariableResolverImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/el/VariableResolverImpl.java
new file mode 100644
index 0000000..33dd712
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/el/VariableResolverImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.el;
+
+import javax.el.ELContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.VariableResolver;
+
+@Deprecated
+public final class VariableResolverImpl implements VariableResolver {
+
+    private final ELContext ctx;
+    
+    public VariableResolverImpl(ELContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Override
+    public Object resolveVariable(String pName) throws ELException {
+        return this.ctx.getELResolver().getValue(this.ctx, null, pName);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings.properties
new file mode 100644
index 0000000..c40f5d6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings.properties
@@ -0,0 +1,487 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# $Id: LocalStrings.properties,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+#
+# Default localized string information
+# Localized this the Default Locale as is en_US
+
+jsp.error.compiler=No Java compiler available
+jsp.error.bad.servlet.engine=Incorrect servlet engine version!
+jsp.error.no.scratch.dir=The JSP engine is not configured with a scratch dir.\
+\n Please add \"jsp.initparams=scratchdir=<dir-name>\" \
+\n in the servlets.properties file for this context.
+jsp.error.bad.scratch.dir=The scratchDir you specified: {0} is unusable.
+jsp.message.scratch.dir.is=Scratch dir for the JSP engine is: {0}
+jsp.message.parent_class_loader_is=Parent class loader is: {0}
+jsp.message.dont.modify.servlets=IMPORTANT: Do not modify the generated servlets
+jsp.error.not.impl.comments=Internal error: Comments not implemented
+jsp.error.not.impl.directives=Internal error: Directives not implemented
+jsp.error.not.impl.declarations=Internal error: Declarations not implemented
+jsp.error.not.impl.expressions=Internal error: Expressions not implemented
+jsp.error.not.impl.scriptlets=Internal error: Scriptlets not implemented
+jsp.error.not.impl.usebean=Internal error: useBean not implemented
+jsp.error.not.impl.getp=Internal error: getProperty not implemented
+jsp.error.not.impl.setp=Internal error: setProperty not implemented
+jsp.error.not.impl.plugin=Internal error: plugin not implemented
+jsp.error.not.impl.forward=Internal error: forward not implemented
+jsp.error.not.impl.include=Internal error: include not implemented
+jsp.error.unavailable=JSP has been marked unavailable
+jsp.error.usebean.missing.attribute=useBean: id attribute missing or misspelled
+jsp.error.usebean.missing.type=useBean ({0}): Either class or type attribute must be \
+specified: 
+jsp.error.usebean.duplicate=useBean: Duplicate bean name: {0}
+jsp.error.usebean.prohibited.as.session=Can't use as session bean {0} since it is prohibited \
+by jsp directive defined earlier: 
+jsp.error.usebean.not.both=useBean: Can't specify both class and beanName attribute: 
+jsp.error.usebean.bad.type.cast=useBean ({0}): Type ({1}) is not assignable from class ({2}) 
+jsp.error.invalid.scope=Illegal value of \'scope\' attribute: {0} (must be one of \"page\", \"request\", \"session\", or \"application\")
+jsp.error.classname=Can't determine classname from .class file
+jsp.error.outputfolder=No output folder
+jsp.warning.bad.type=Warning: bad type in .class file
+jsp.error.data.file.write=Error while writing data file
+jsp.error.page.conflict.contenttype=Page directive: illegal to have multiple occurrences of 'contentType' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.contenttype=Page directive: invalid value for contentType
+jsp.error.page.conflict.session=Page directive: illegal to have multiple occurrences of 'session' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.session=Page directive: invalid value for session
+jsp.error.page.conflict.buffer=Page directive: illegal to have multiple occurrences of 'buffer' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.buffer=Page directive: invalid value for buffer
+jsp.error.page.conflict.autoflush=Page directive: illegal to have multiple occurrences of 'autoFlush' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.autoflush=Page directive: invalid value for autoFlush
+jsp.error.page.conflict.isthreadsafe=Page directive: illegal to have multiple occurrences of 'isThreadSafe' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.isthreadsafe=Page directive: invalid value for isThreadSafe
+jsp.error.page.conflict.info=Page directive: illegal to have multiple occurrences of 'info' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.info=Page directive: invalid value for info
+jsp.error.page.conflict.iserrorpage=Page directive: illegal to have multiple occurrences of 'isErrorPage' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.iserrorpage=Page directive: invalid value for isErrorPage
+jsp.error.page.conflict.errorpage=Page directive: illegal to have multiple occurrences of 'errorPage' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.language=Page directive: illegal to have multiple occurrences of 'language' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.language=Tag directive: illegal to have multiple occurrences of 'language' with different values (old: {0}, new: {1})
+jsp.error.page.language.nonjava=Page directive: invalid language attribute
+jsp.error.tag.language.nonjava=Tag directive: invalid language attribute
+jsp.error.page.defafteruse.language=Page directive: can't define language after a scriptlet 
+jsp.error.page.nomapping.language=Page directive: No mapping for language: 
+jsp.error.page.conflict.extends=Page directive: illegal to have multiple occurrences of 'extends' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.iselignored=Page directive: illegal to have multiple occurrences of 'isELIgnored' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.iselignored=Tag directive: illegal to have multiple occurrences of 'isELIgnored' with different values (old: {0}, new: {1})
+jsp.error.page.invalid.iselignored=Page directive: invalid value for isELIgnored
+jsp.error.tag.invalid.iselignored=Tag directive: invalid value for isELIgnored
+jsp.error.page.multi.pageencoding=Page directive must not have multiple occurrences of pageencoding
+jsp.error.tag.conflict.attr=Tag directive: illegal to have multiple occurrences of the attribute \"{0}\" with different values (old: {1}, new: {2})
+jsp.error.tag.multi.pageencoding=Tag directive must not have multiple occurrences of pageencoding
+jsp.error.page.bad_b_and_a_combo=Page directive: Illegal combination of buffer=\"none\" && autoFlush=\"false\"
+jsp.error.not.impl.taglib=Internal error: Tag extensions not implemented
+jsp.error.include.missing.file=Missing file argument to include
+jsp.error.include.bad.file=Bad file argument to include 
+jsp.error.include.exception=Unable to include {0}
+jsp.error.stream.close.failed=Failed to close stream
+jsp.error.stream.closed=Stream closed
+jsp.error.invalid.forward=Invalid forward tag
+jsp.error.unknownException=Unhandled error! You might want to consider having an error page \
+to report such errors more gracefully
+jsp.error.invalid.directive=Invalid directive
+jsp.error.invalid.implicit=Invalid implicit TLD for tag file at {0}
+jsp.error.invalid.implicit.version=Invalid JSP version defined in implicit TLD for tag file at {0}
+jsp.error.invalid.version=Invalid JSP version defined for tag file at {0}
+jsp.error.directive.istagfile={0} directive cannot be used in a tag file
+jsp.error.directive.isnottagfile={0} directive can only be used in a tag file
+jsp.error.tagfile.tld.name=The \"name\" attribute of the tag directive has a value {0} while the \"name\" tag of the \"tag-file\" element in the TLD is {1}
+jsp.error.action.istagfile={0} action cannot be used in a tag file
+jsp.error.action.isnottagfile={0} action can be used in tag files only
+jsp.error.unterminated=Unterminated {0} tag
+jsp.error.usebean.notinsamefile=useBean tag must begin and end in the same physical file
+jsp.error.loadclass.taghandler=Unable to load tag handler class \"{0}\" for tag \"{1}\"
+jsp.error.unable.compile=Unable to compile class for JSP
+jsp.error.unable.load=Unable to load class for JSP
+jsp.error.unable.rename=Unable to rename class file {0} to {1}
+jsp.error.mandatory.attribute={0}: Mandatory attribute {1} missing
+jsp.error.flush=Exception occurred when flushing data
+jsp.engine.info=Jasper JSP 2.2 Engine
+jsp.error.invalid.expression="{0}" contains invalid expression(s): {1}
+jsp.error.invalid.attribute={0} has invalid attribute: {1}
+jsp.error.usebean.class.notfound=Class: {0} not found
+jsp.error.file.cannot.read=Cannot read file: {0}
+jsp.error.file.already.registered=Recursive include of file {0}
+jsp.error.file.not.registered=file {0} not seen in include
+jsp.error.quotes.unterminated=Unterminated quotes
+jsp.error.attr.quoted=Attribute value should be quoted
+jsp.error.attr.novalue=Attribute {0} has no value
+jsp.error.tag.attr.unterminated=Unterminated tag attribute list
+jsp.error.param.noname=No name in PARAM tag
+jsp.error.param.novalue=No value in PARAM tag
+jsp.error.beans.nullbean=Attempted a bean operation on a null object.
+jsp.error.beans.nobeaninfo=No BeanInfo for the bean of type ''{0}'' could be found, the class likely does not exist.
+jsp.error.beans.introspection=An exception occurred while introspecting the read method of property ''{0}'' in a bean of type ''{1}'':\n{2}
+jsp.error.beans.nomethod=Cannot find a method to read property ''{0}'' in a bean of type ''{1}''
+jsp.error.beans.nomethod.setproperty=Can''t find a method to write property ''{0}'' of type ''{1}'' in a bean of type ''{2}''
+jsp.error.beans.noproperty=Cannot find any information on property ''{0}'' in a bean of type ''{1}''
+jsp.error.beans.property.conversion=Unable to convert string \"{0}\" to class \"{1}\" for attribute \"{2}\": {3}
+jsp.error.beans.propertyeditor.notregistered=Property Editor not registered with the PropertyEditorManager
+jsp.error.beans.setproperty.noindexset=Cannot set indexed property
+jsp.error.include.tag=Invalid jsp:include tag
+jsp.error.include.noflush=jsp:include needs to have \"flush=true\"
+jsp.error.include.badflush=jsp:include page=\"...\" flush=\"true\" is the only valid combination in JSP 1.0
+jsp.error.attempt_to_clear_flushed_buffer=Error: Attempt to clear a buffer that's already been flushed
+jsp.error.overflow=Error: JSP Buffer overflow
+jsp.error.paramexpected=Expecting \"jsp:param\" standard action with \"name\" and \"value\" attributes
+jsp.error.param.invalidUse=The jsp:param action must not be used outside the jsp:include, jsp:forward, or jsp:params elements
+jsp.error.params.invalidUse=jsp:params must be a direct child of jsp:plugin
+jsp.error.fallback.invalidUse=jsp:fallback must be a direct child of jsp:plugin
+jsp.error.namedAttribute.invalidUse=jsp:attribute must be the subelement of a standard or custom action
+jsp.error.jspbody.invalidUse=jsp:body must be the subelement of a standard or custom action
+jsp.error.closeindividualparam=param tag needs to be closed with \"/>\"
+jsp.error.closeparams=param tag needs to be closed with /params
+jsp.error.params.emptyBody=jsp:params must contain at least one nested jsp:param
+jsp.error.params.illegalChild=jsp:params must not have any nested elements other than jsp:param
+jsp.error.plugin.notype=type not declared in jsp:plugin
+jsp.error.plugin.badtype=Illegal value for 'type' attribute in jsp:plugin: must be 'bean' or 'applet'
+jsp.error.plugin.nocode=code not declared in jsp:plugin
+jsp.error.ise_on_clear=Illegal to clear() when buffer size == 0
+jsp.error.setproperty.beanNotFound=setProperty: Bean {0} not found
+jsp.error.getproperty.beanNotFound=getProperty: Bean {0} not found
+jsp.error.setproperty.ClassNotFound=setProperty: Class {0} not found
+jsp.error.javac=Javac exception
+jsp.error.javac.env=Environment: 
+jsp.error.compilation=Error compiling file: {0} {1}
+jsp.error.setproperty.invalidSyntax=setProperty: can't have non-null value when property=*
+jsp.error.setproperty.beanInfoNotFound=setproperty: beanInfo for bean {0} not found
+jsp.error.setproperty.paramOrValue=setProperty: either param or value can be present
+jsp.error.setproperty.arrayVal=setProperty: can't set array property {0} through a string constant value
+jsp.error.undeclared_namespace=A custom tag was encountered with an undeclared namespace [{0}] 
+jsp.warning.keepgen=Warning: Invalid value for the initParam keepgenerated. Will use the default value of \"false\"
+jsp.warning.xpoweredBy=Warning: Invalid value for the initParam xpoweredBy. Will use the default value of \"false\"
+jsp.warning.enablePooling=Warning: Invalid value for the initParam enablePooling. Will use the default value of \"true\"
+jsp.warning.invalidTagPoolSize=Warning: Invalid value for the init parameter named tagPoolSize. Will use default size of {0}
+jsp.warning.mappedFile=Warning: Invalid value for the initParam mappedFile. Will use the default value of \"false\"
+jsp.warning.classDebugInfo=Warning: Invalid value for the initParam classdebuginfo. Will use the default value of \"false\"
+jsp.warning.checkInterval=Warning: Invalid value for the initParam checkInterval. Will use the default value of \"300\" seconds
+jsp.warning.modificationTestInterval=Warning: Invalid value for the initParam modificationTestInterval. Will use the default value of \"4\" seconds
+jsp.warning.recompileOnFail=Warning: Invalid value for the initParam recompileOnFail. Will use the default value of \"false\"
+jsp.warning.development=Warning: Invalid value for the initParam development. Will use the default value of \"true\"
+jsp.warning.fork=Warning: Invalid value for the initParam fork. Will use the default value of \"true\"
+jsp.warning.reloading=Warning: Invalid value for the initParam reloading. Will use the default value of \"true\"
+jsp.warning.dumpSmap=Warning: Invalid value for the initParam dumpSmap. Will use the default value of \"false\"
+jsp.warning.genchararray=Warning: Invalid value for the initParam genStrAsCharArray. Will use the default value of \"false\"
+jsp.warning.suppressSmap=Warning: Invalid value for the initParam suppressSmap. Will use the default value of \"false\"
+jsp.warning.displaySourceFragment=Warning: Invalid value for the initParam displaySourceFragment. Will use the default value of \"true\"
+jsp.warning.maxLoadedJsps=Warning: Invalid value for the initParam maxLoadedJsps. Will use the default value of \"-1\"
+jsp.warning.jspIdleTimeout=Warning: Invalid value for the initParam jspIdleTimeout. Will use the default value of \"-1\"
+jsp.error.badtaglib=Unable to open taglibrary {0} : {1}
+jsp.error.badGetReader=Cannot create a reader when the stream is not buffered
+jsp.warning.unknown.element.in.taglib=Unknown element ({0}) in taglib
+jsp.warning.unknown.element.in.tag=Unknown element ({0}) in tag
+jsp.warning.unknown.element.in.tagfile=Unknown element ({0}) in tag-file
+jsp.warning.unknown.element.in.attribute=Unknown element ({0}) in attribute
+jsp.warning.unknown.element.in.variable=Unknown element ({0}) in variable
+jsp.warning.unknown.element.in.validator=Unknown element ({0}) in validator
+jsp.warning.unknown.element.in.initParam=Unknown element ({0}) in validator's init-param
+jsp.warning.unknown.element.in.function=Unknown element ({0}) in function
+jsp.error.more.than.one.taglib=More than one taglib in the TLD: {0}
+jsp.error.teiclass.instantiation=Failed to load or instantiate TagExtraInfo class: {0}
+jsp.error.non_null_tei_and_var_subelems=Tag {0} has one or more variable subelements and a TagExtraInfo class that returns one or more VariableInfo
+jsp.error.parse.error.in.TLD=Parse Error in the tag library descriptor: {0}
+jsp.error.unable.to.open.TLD=Unable to open the tag library descriptor: {0}
+jsp.buffer.size.zero=Buffer size <= 0
+jsp.error.file.not.found=File \"{0}\" not found
+jsp.message.copyinguri=Copying {0} into {1}
+jsp.message.htmlcomment=\nStripping Comment: \t{0}
+jsp.message.handling_directive=\nHandling Directive: {0}\t{1}
+jsp.message.handling_plugin=\nPlugin: {0}
+jsp.message.package_name_is=Package name is: {0}
+jsp.message.class_name_is=Class name is: {0}
+jsp.message.java_file_name_is=Java file name is: {0}
+jsp.message.class_file_name_is=Class file name is: {0}
+jsp.message.accepted=Accepted {0} at {1}
+jsp.message.adding_jar=Adding jar {0} to my classpath
+jsp.message.compiling_with=Compiling with: {0}
+jsp.message.template_text=template text
+jsp.error.missing_attribute=According to the TLD or the tag file, attribute {0} is mandatory for tag {1}
+jsp.error.bad_attribute=Attribute {0} invalid for tag {1} according to TLD
+jsp.error.tld.unable_to_read=Unable to read TLD \"{1}\" from JAR file \"{0}\": {2}
+jsp.error.tld.unable_to_get_jar=Unable to get JAR resource \"{0}\" containing TLD: {1}
+jsp.error.tld.missing_jar=Missing JAR resource \"{0}\" containing TLD
+jsp.error.webxml_not_found=Could not locate web.xml
+jsp.cmd_line.usage=Usage: jsptoservlet [-dd <path/to/outputDirectory>] [-keepgenerated] \
+<.jsp files>
+jsp.message.cp_is=Classpath {0} is: {1}
+jsp.error.unable.to_load_taghandler_class=Unable to load tag handler class {0} because of {1}
+jsp.error.unable.to_find_method=Unable to find setter method for attribute: {0}
+jsp.error.unable.to_convert_string=Unable to convert a String to {0} for attribute {1}
+jsp.error.unable.to_introspect=Unable to introspect on tag handler class: {0} because of {1}
+jsp.error.bad_tag=No tag \"{0}\" defined in tag library imported with prefix \"{1}\"
+jsp.error.xml.bad_tag=No tag \"{0}\" defined in tag library associated with uri \"{1}\"
+jsp.error.bad_string_Character=Cannot extract a Character from a zero length array
+jsp.error.bad_string_char=Cannot extract a char from a zero length array
+jsp.warning.compiler.class.cantcreate=Can't create an instance of specified compiler plugin class {0} due to {1}. Will default to Sun Java Compiler.
+jsp.warning.compiler.class.notfound=Specified compiler plugin class {0} not found. Will default to Sun Java Compiler.
+jsp.warning.compiler.path.notfound=Specified compiler path {0} not found. Will default to system PATH.
+jsp.warning.compiler.classfile.delete.fail=Failed to delete generated class file [{0}] 
+jsp.warning.compiler.javafile.delete.fail=Failed to delete generated Java file [{0}] 
+jsp.error.jspc.uriroot_not_dir=The -uriroot option must specify a pre-existing directory
+jsp.error.jspc.missingTarget=Missing target: Must specify -webapp or -uriroot, or one or more JSP pages
+jsp.error.jspc.no_uriroot=The uriroot is not specified and cannot be located with the specified JSP file(s)
+jspc.implicit.uriRoot=uriRoot implicitly set to "{0}"
+jspc.usage=Usage: jspc <options> [--] <jsp files>\n\
+where jsp files is\n\
+\    -webapp <dir>      A directory containing a web-app, whose JSP pages\n\
+\                       will be processed recursively\n\
+or any number of\n\
+\    <file>             A file to be parsed as a JSP page\n\
+where options include:\n\
+\    -help              Print this help message\n\
+\    -v                 Verbose mode\n\
+\    -d <dir>           Output Directory (default -Djava.io.tmpdir)\n\
+\    -l                 Outputs the name of the JSP page upon failure\n\
+\    -s                 Outputs the name of the JSP page upon success\n\
+\    -p <name>          Name of target package (default org.apache.jsp)\n\
+\    -c <name>          Name of target class name (only applies to first JSP page)\n\
+\    -mapped            Generates separate write() calls for each HTML line in the JSP\n\
+\    -die[#]            Generates an error return code (#) on fatal errors (default 1)\n\
+\    -uribase <dir>     The uri directory compilations should be relative to\n\
+\                       (default "/")\n\
+\    -uriroot <dir>     Same as -webapp\n\
+\    -compile           Compiles generated servlets\n\
+\    -webinc <file>     Creates a partial servlet mappings in the file\n\
+\    -webxml <file>     Creates a complete web.xml in the file\n\
+\    -webxmlencoding <enc> Set the encoding charset used to read and write the web.xml\n\
+\                       file (default is platform default encoding)\n\
+\    -addwebxmlmappings Merge generated web.xml fragment into the web.xml file of the\n\
+\                       web-app, whose JSP pages we are processing\n\
+\    -ieplugin <clsid>  Java Plugin classid for Internet Explorer\n\
+\    -classpath <path>  Overrides java.class.path system property\n\
+\    -xpoweredBy        Add X-Powered-By response header\n\
+\    -trimSpaces        Trim spaces in template text between actions, directives\n\
+\    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.6)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.6)\n\
+
+jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
+\n\
+<!DOCTYPE web-app\n\
+\    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+\    "http://java.sun.com/dtd/web-app_2_3.dtd">\n\
+<!--\n\
+Automatically created by Apache Tomcat JspC.\n\
+-->\n\
+<web-app>\n\
+\n
+jspc.webxml.footer=\n\
+</web-app>\n\
+\n
+jspc.webinc.header=\n\
+<!--\n\
+Automatically created by Apache Tomcat JspC.\n\
+Place this fragment in the web.xml before all icon, display-name,\n\
+description, distributable, and context-param elements.\n\
+-->\n
+jspc.webinc.footer=\n\
+<!--\n\
+All session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+resource-ref, security-constraint, login-config, security-role,\n\
+env-entry, and ejb-ref elements should follow this fragment.\n\
+-->\n
+jspc.webinc.insertEnd=<!-- JSPC servlet mappings end -->
+jspc.webinc.insertStart=<!-- JSPC servlet mappings start -->
+jspc.error.jasperException=error-the file ''{0}'' generated the following parse exception: {1}
+jspc.error.generalException=ERROR-the file ''{0}'' generated the following general exception:
+jspc.error.fileDoesNotExist=The file argument ''{0}'' does not exist
+jspc.error.emptyWebApp=-webapp requires a trailing file argument
+jspc.delete.fail=Failed to delete file [{0}]
+jsp.error.library.invalid=JSP page is invalid according to library {0}: {1}
+jsp.error.tlvclass.instantiation=Failed to load or instantiate TagLibraryValidator class: {0}
+jsp.error.tlv.invalid.page=Validation error messages from TagLibraryValidator for {0} in {1}
+jsp.error.tei.invalid.attributes=Validation error messages from TagExtraInfo for {0}
+jsp.parser.sax.propertynotsupported=SAX property not supported: {0}
+jsp.parser.sax.propertynotrecognized=SAX property not recognized: {0}
+jsp.parser.sax.featurenotsupported=SAX feature not supported: {0}
+jsp.parser.sax.featurenotrecognized=SAX feature not recognized: {0}
+jsp.error.no.more.content=End of content reached while more parsing required: tag nesting error?
+jsp.error.parse.xml=XML parsing error on file {0}
+jsp.error.parse.xml.line=XML parsing error on file {0}: (line {1}, col {2})
+jsp.error.parse.xml.scripting.invalid.body=Body of {0} element must not contain any XML elements
+jsp.error.internal.tldinit=Unable to initialize TldLocationsCache: {0}
+jsp.error.internal.filenotfound=Internal Error: File {0} not found
+jsp.error.internal.evaluator_not_found=Internal error: unable to load expression evaluator
+jsp.error.parse.xml.invalidPublicId=Invalid PUBLIC ID: {0}
+jsp.error.include.flush.invalid.value=Invalid value for the flush attribute: {0}
+jsp.error.unsupported.encoding=Unsupported encoding: {0}
+tld.error.variableNotAllowed=It is an error for a tag that has one or more variable subelements to have a TagExtraInfo class that returns a non-null object.
+jsp.error.tldInWebDotXmlNotFound=Could not locate TLD {1} for URI {0} specified in web.xml
+jsp.error.taglibDirective.absUriCannotBeResolved=The absolute uri: {0} cannot be resolved in either web.xml or the jar files deployed with this application
+jsp.error.taglibDirective.missing.location=Neither \'uri\' nor \'tagdir\' attribute specified
+jsp.error.taglibDirective.both_uri_and_tagdir=Both \'uri\' and \'tagdir\' attributes specified
+jsp.error.invalid.tagdir=Tag file directory {0} does not start with \"/WEB-INF/tags\"
+jsp.error.unterminated.user.tag=Unterminated user-defined tag: ending tag {0} not found or incorrectly nested
+#jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:text&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+#Error while processing taglib jar file {0}: {1}
+jsp.error.taglib.reserved.prefix=The taglib prefix {0} is reserved
+jsp.error.invalid.javaEncoding=Invalid java encodings. Tried {0} and then {1}. Both failed.
+jsp.error.needAlternateJavaEncoding=Default java encoding {0} is invalid on your java platform. An alternate can be specified via the 'javaEncoding' parameter of JspServlet.
+#Error when compiling, used for jsp line number error messages
+jsp.error.single.line.number=An error occurred at line: {0} in the jsp file: {1}
+jsp.error.multiple.line.number=\n\nAn error occurred between lines: {0} and {1} in the jsp file: {2}\n\n
+jsp.error.java.line.number=An error occurred at line: {0} in the generated java file
+jsp.error.corresponding.servlet=Generated servlet error:\n
+jsp.error.empty.body.not.allowed=Empty body not allowed for {0}
+jsp.error.jspbody.required=Must use jsp:body to specify tag body for {0} if jsp:attribute is used.
+jsp.error.jspbody.emptybody.only=The {0} tag can only have jsp:attribute in its body.
+jsp.error.no.scriptlets=Scripting elements ( &lt;%!, &lt;jsp:declaration, &lt;%=, &lt;jsp:expression, &lt;%, &lt;jsp:scriptlet ) are disallowed here.
+jsp.error.internal.unexpected_node_type=Internal Error: Unexpected node type encountered
+jsp.error.tld.fn.invalid.signature=Invalid syntax for function signature in TLD.  Tag Library: {0}, Function: {1}
+jsp.error.tld.fn.duplicate.name=Duplicate function name {0} in tag library {1}
+jsp.error.tld.fn.invalid.signature.commaexpected=Invalid syntax for function signature in TLD.  Comma ',' expected.  Tag Library: {0}, Function: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected=Invalid syntax for function signature in TLD.  Parenthesis '(' expected.  Tag Library: {0}, Function: {1}.
+jsp.error.tld.mandatory.element.missing=Mandatory TLD element {0} missing or empty in TLD {1} 
+jsp.error.dynamic.attributes.not.implemented=The {0} tag declares that it accepts dynamic attributes but does not implement the required interface
+jsp.error.nomatching.fragment=Cannot find an attribute directive (with name={0} and fragment=true) prior to the fragment directive.
+jsp.error.attribute.noequal=equal symbol expected
+jsp.error.attribute.noquote=quote symbol expected
+jsp.error.attribute.unterminated=attribute for {0} is not properly terminated
+jsp.error.attribute.noescape=Attribute value {0} is quoted with {1} which must be escaped when used within the value
+jsp.error.attribute.nowhitespace=The JSP specification requires that an attribute name is preceded by whitespace
+jsp.error.attribute.duplicate=Attribute qualified names must be unique within an element
+jsp.error.missing.tagInfo=TagInfo object for {0} is missing from TLD
+jsp.error.deferredmethodsignaturewithoutdeferredmethod=Cannot specify a method signature if 'deferredMethod' is not 'true'
+jsp.error.deferredvaluetypewithoutdeferredvalue=Cannot specify a value type if 'deferredValue' is not 'true'
+jsp.error.deferredmethodandvalue='deferredValue' and 'deferredMethod' cannot be both 'true'
+jsp.error.fragmentwithtype=Cannot specify both 'fragment' and 'type' attributes.  If 'fragment' is present, 'type' is fixed as 'javax.servlet.jsp.tagext.JspFragment'
+jsp.error.fragmentwithrtexprvalue=Cannot specify both 'fragment' and 'rtexprvalue' attributes.  If 'fragment' is present, 'rtexprvalue' is fixed as 'true'
+jsp.error.fragmentWithDeclareOrScope=Both 'fragment' and 'declare' or 'scope' attributes specified in variable directive
+jsp.error.var_and_varReader=Only one of \'var\' or \'varReader\' may be specified
+jsp.error.missing_var_or_varReader=Missing \'var\' or \'varReader\' attribute
+jsp.warning.bad.urlpattern.propertygroup=Bad value {0} in the url-pattern subelement in web.xml
+jsp.error.literal_with_void=A literal value was specified for attribute {0} that is defined as a deferred method with a return type of void. JSP.2.3.4 does not permit literal values in this case
+jsp.error.unknown_attribute_type=Unknown attribute type ({1}) for attribute {0}.
+jsp.error.coerce_to_type=Cannot coerce value ({2}) to type ({1}) for attribute {0}.
+jsp.error.jspelement.missing.name=Mandatory XML-style \'name\' attribute missing
+jsp.error.xmlns.redefinition.notimplemented=Internal error: Attempt to redefine xmlns:{0}.  Redefinition of namespaces is not implemented.
+jsp.error.could.not.add.taglibraries=Could not add one or more tag libraries.
+jsp.error.duplicate.name.jspattribute=The attribute {0} specified in the standard or custom action also appears as the value of the name attribute in the enclosed jsp:attribute
+jsp.error.not.in.template={0} not allowed in a template text body.
+jsp.error.badStandardAction=Invalid standard action
+jsp.error.xml.badStandardAction=Invalid standard action: {0}
+jsp.error.tagdirective.badbodycontent=Invalid body-content ({0}) in tag directive
+jsp.error.simpletag.badbodycontent=The TLD for the class {0} specifies an invalid body-content (JSP) for a SimpleTag.
+jsp.error.config_pagedir_encoding_mismatch=Page-encoding specified in jsp-property-group ({0}) is different from that specified in page directive ({1})
+jsp.error.prolog_pagedir_encoding_mismatch=Page-encoding specified in XML prolog ({0}) is different from that specified in page directive ({1})
+jsp.error.prolog_config_encoding_mismatch=Page-encoding specified in XML prolog ({0}) is different from that specified in jsp-property-group ({1})
+jsp.error.attribute.custom.non_rt_with_expr=According to TLD or attribute directive in tag file, attribute {0} does not accept any expressions
+jsp.error.attribute.standard.non_rt_with_expr=The {0} attribute of the {1} standard action does not accept any expressions
+jsp.error.attribute.deferredmix=Cannot use both ${} and #{} EL expressions in the same attribute value
+jsp.error.scripting.variable.missing_name=Unable to determine scripting variable name from attribute {0}
+jasper.error.emptybodycontent.nonempty=According to TLD, tag {0} must be empty, but is not
+jsp.error.tagfile.nameNotUnique=The value of {0} and the value of {1} in line {2} are the same.
+jsp.error.tagfile.nameFrom.noAttribute=Cannot find an attribute directive with a name attribute with a value \"{0}\", the value of this name-from-attribute attribute.
+jsp.error.tagfile.nameFrom.badAttribute=The attribute directive (declared in line {1} and whose name attribute is \"{0}\", the value of this name-from-attribute attribute) must be of type java.lang.String, is \"required\" and not a \"rtexprvalue\".
+jsp.error.page.noSession=Cannot access session scope in page that does not participate in any session
+jsp.error.usebean.noSession=Illegal for useBean to use session scope when JSP page declares (via page directive) that it does not participate in sessions
+jsp.error.xml.encodingByteOrderUnsupported = Given byte order for encoding \"{0}\" is not supported.
+jsp.error.xml.encodingDeclInvalid = Invalid encoding name \"{0}\".
+jsp.error.xml.encodingDeclRequired = The encoding declaration is required in the text declaration.
+jsp.error.xml.morePseudoAttributes = more pseudo attributes is expected.
+jsp.error.xml.noMorePseudoAttributes = no more pseudo attributes is allowed.
+jsp.error.xml.versionInfoRequired = The version is required in the XML declaration.
+jsp.error.xml.xmlDeclUnterminated = The XML declaration must end with \"?>\".
+jsp.error.xml.reservedPITarget = The processing instruction target matching \"[xX][mM][lL]\" is not allowed.
+jsp.error.xml.spaceRequiredInPI = White space is required between the processing instruction target and data.
+jsp.error.xml.invalidCharInContent = An invalid XML character (Unicode: 0x{0}) was found in the element content of the document.
+jsp.error.xml.spaceRequiredBeforeStandalone = White space is required before the encoding pseudo attribute in the XML declaration.
+jsp.error.xml.sdDeclInvalid = The standalone document declaration value must be \"yes\" or \"no\", not \"{0}\".
+jsp.error.xml.invalidCharInPI = An invalid XML character (Unicode: 0x{0}) was found in the processing instruction.
+jsp.error.xml.versionNotSupported = XML version \"{0}\" is not supported, only XML 1.0 is supported.
+jsp.error.xml.pseudoAttrNameExpected = a pseudo attribute name is expected.
+jsp.error.xml.expectedByte = Expected byte {0} of {1}-byte UTF-8 sequence.
+jsp.error.xml.invalidByte = Invalid byte {0} of {1}-byte UTF-8 sequence.
+jsp.error.xml.operationNotSupported = Operation \"{0}\" not supported by {1} reader.
+jsp.error.xml.invalidHighSurrogate = High surrogate bits in UTF-8 sequence must not exceed 0x10 but found 0x{0}.
+jsp.error.xml.invalidASCII = Byte \"{0}\" not 7-bit ASCII.
+jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl = White space is required before the encoding pseudo attribute in the XML declaration.
+jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl = White space is required before the encoding pseudo attribute in the text declaration.
+jsp.error.xml.spaceRequiredBeforeVersionInTextDecl = White space is required before the version pseudo attribute in the text declaration.
+jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl = White space is required before the version pseudo attribute in the XML declaration.
+jsp.error.xml.eqRequiredInXMLDecl = The '' = '' character must follow \"{0}\" in the XML declaration.
+jsp.error.xml.eqRequiredInTextDecl = The '' = '' character must follow \"{0}\" in the text declaration.
+jsp.error.xml.quoteRequiredInTextDecl = The value following \"{0}\" in the text declaration must be a quoted string.
+jsp.error.xml.quoteRequiredInXMLDecl = The value following \"{0}\" in the XML declaration must be a quoted string.
+jsp.error.xml.invalidCharInTextDecl = An invalid XML character (Unicode: 0x{0}) was found in the text declaration.
+jsp.error.xml.invalidCharInXMLDecl = An invalid XML character (Unicode: 0x{0}) was found in the XML declaration.
+jsp.error.xml.closeQuoteMissingInTextDecl = closing quote in the value following \"{0}\" in the text declaration is missing.
+jsp.error.xml.closeQuoteMissingInXMLDecl = closing quote in the value following \"{0}\" in the XML declaration is missing.
+jsp.error.multiple.jsp = Cannot have multiple specifications of 
+jsp.error.jspoutput.conflict=&lt;jsp:output&gt;: illegal to have multiple occurrences of \"{0}\" with different values (old: {1}, new: {2})
+jsp.error.jspoutput.doctypenamesystem=&lt;jsp:output&gt;: 'doctype-root-element' and 'doctype-system' attributes must appear together
+jsp.error.jspoutput.doctypepulicsystem=&lt;jsp:output&gt;: 'doctype-system' attribute must appear if 'doctype-public' attribute appears
+jsp.error.jspoutput.nonemptybody=&lt;jsp:output&gt; must not have a body
+jsp.error.jspoutput.invalidUse=&lt;jsp:output&gt; must not be used in standard syntax
+jsp.error.attributes.not.allowed = {0} must not have any attributes
+jsp.error.tagfile.badSuffix=Missing \".tag\" suffix in tag file path {0}
+jsp.error.tagfile.illegalPath=Illegal tag file path: {0}, must start with \"/WEB-INF/tags\" or \"/META-INF/tags\"
+jsp.error.plugin.wrongRootElement=Name of root element in {0} different from {1}
+jsp.error.attribute.invalidPrefix=The attribute prefix {0} does not correspond to any imported tag library
+jsp.error.nested.jspattribute=A jsp:attribute standard action cannot be nested within another jsp:attribute standard action
+jsp.error.nested.jspbody=A jsp:body standard action cannot be nested within another jsp:body or jsp:attribute standard action
+jsp.error.variable.either.name=Either name-given or name-from-attribute attribute must be specified in a variable directive
+jsp.error.variable.both.name=Cannot specify both name-given or name-from-attribute attributes in a variable directive
+jsp.error.variable.alias=Both or none of the name-from-attribute and alias attributes must be specified in a variable directive
+jsp.error.attribute.null_name=Null attribute name
+jsp.error.jsptext.badcontent=\'&lt;\', when appears in the body of &lt;jsp:text&gt;, must be encapsulated within a CDATA
+jsp.error.jsproot.version.invalid=Invalid version number: \"{0}\", must be \"1.2\", \"2.0\", \"2.1\" or \"2.2\"
+jsp.error.noFunctionPrefix=The function {0} must be used with a prefix when a default namespace is not specified
+jsp.error.noFunction=The function {0} cannot be located with the specified prefix
+jsp.error.noFunctionMethod=Method \"{0}\" for function \"{1}\" not found in class \"{2}\"
+jsp.error.function.classnotfound=The class {0} specified in TLD for the function {1} cannot be found: {2}
+jsp.error.signature.classnotfound=The class {0} specified in the method signature in TLD for the function {1} cannot be found. {2}
+jsp.error.text.has_subelement=&lt;jsp:text&gt; must not have any subelements
+jsp.error.data.file.read=Error reading file \"{0}\"
+jsp.error.prefix.refined=Attempt to redefine the prefix {0} to {1}, when it was already defined as {2} in the current scope.
+jsp.error.nested_jsproot=Nested &lt;jsp:root&gt;
+jsp.error.unbalanced.endtag=The end tag \"&lt;/{0}\" is unbalanced
+jsp.error.invalid.bean=The value for the useBean class attribute {0} is invalid.
+jsp.error.prefix.use_before_dcl=The prefix {0} specified in this tag directive has been previously used by an action in file {1} line {2}.
+
+jsp.exception=An exception occurred processing JSP page {0} at line {1}
+
+# JSP 2.1
+jsp.error.el.template.deferred=#{...} is not allowed in template text
+jsp.error.el.parse={0} : {1}
+jsp.error.page.invalid.deferredsyntaxallowedasliteral=Page directive: invalid value for deferredSyntaxAllowedAsLiteral
+jsp.error.tag.invalid.deferredsyntaxallowedasliteral=Tag directive: invalid value for deferredSyntaxAllowedAsLiteral
+jsp.error.page.conflict.deferredsyntaxallowedasliteral=Page directive: illegal to have multiple occurrences of 'deferredSyntaxAllowedAsLiteral' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.deferredsyntaxallowedasliteral=Tag directive: illegal to have multiple occurrences of 'deferredSyntaxAllowedAsLiteral' with different values (old: {0}, new: {1})
+
+jsp.error.page.invalid.trimdirectivewhitespaces=Page directive: invalid value for trimDirectiveWhitespaces
+jsp.error.tag.invalid.trimdirectivewhitespaces=Tag directive: invalid value for trimDirectiveWhitespaces
+jsp.error.page.conflict.trimdirectivewhitespaces=Page directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.trimdirectivewhitespaces=Tag directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
+
+# JarScanner
+jsp.warning.noJarScanner=Warning: No org.apache.tomcat.JarScanner set in ServletContext. Falling back to default JarScanner implementation.
+
+# JavacErrorDetail
+jsp.error.bug48498=Unable to display JSP extract. Probably due to an XML parser bug (see Tomcat bug 48498 for details).
+
+# UniqueAttributesImpl
+jsp.error.duplicateqname=An attribute with duplicate qualified name [{0}] was found. Attribute qualified names must be unique within an element.
+
+# JSP unloading handling
+jsp.message.jsp_queue_created=Created jsp queue with length {0} for context [{1}]
+jsp.message.jsp_added=Adding JSP for path [{0}] to queue of context [{1}]
+jsp.message.jsp_queue_update=Updating JSP for path [{0}] in queue of context [{1}]
+jsp.message.jsp_removed_excess=Removing excess JSP for path [{0}] from queue of context [{1}]
+jsp.message.jsp_removed_idle=Removing idle JSP for path [{0}] in context [{1}] after {2} seconds");
+jsp.message.jsp_unload_check=Checking JSPs for unload in context [{0}], JSP count: {1} queue length: {2}
+
+xmlParser.skipBomFail=Failed to skip BOM when parsing XML input stream
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_es.properties
new file mode 100644
index 0000000..01492de
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_es.properties
@@ -0,0 +1,454 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# $Id: LocalStrings_es.properties,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+#
+# Default localized string information
+# Localized para Locale es_ES
+jsp.error.compiler = No hay compilador Java disponible
+jsp.error.bad.servlet.engine = \u00A1Versi\u00F3n incorrecta del motor servlet\!
+jsp.error.no.scratch.dir = El motor JSP no tiene configurado un directorio de trabajo.\n\
+    \ A\u00F1ada "jsp.initparams\=scratchdir\=<dir-name>" \n\
+    \ en el fichero servlets.properties para este contexto.
+jsp.error.bad.scratch.dir = El directorio de trabajo especificado\: {0} no es utilizable.
+jsp.message.scratch.dir.is = El directorio de trabajo para el motor JSP es\: {0}
+jsp.message.parent_class_loader_is = El cargador de clases es\: {0}
+jsp.message.dont.modify.servlets = IMPORTANTE\: No modifique los servlets generados
+jsp.error.not.impl.comments = Error Interno\: Comments no implementado
+jsp.error.not.impl.directives = Error Interno\: Directives no implementado
+jsp.error.not.impl.declarations = Error Interno\: Declarations no implementado
+jsp.error.not.impl.expressions = Error Interno\: Expressions no implementado
+jsp.error.not.impl.scriptlets = Error Interno\: Scriptlets no implementado
+jsp.error.not.impl.usebean = Error Interno\: useBean no implementado
+jsp.error.not.impl.getp = Error Interno\: getProperty no implementado
+jsp.error.not.impl.setp = Error Interno\: setProperty no implementado
+jsp.error.not.impl.plugin = Error Interno\: plugin no implementado
+jsp.error.not.impl.forward = Error Interno\: forward no implementado
+jsp.error.not.impl.include = Error Interno\: include no implementado
+jsp.error.unavailable = JSP ha sido marcado como no disponible
+jsp.error.usebean.missing.attribute = useBean\: falta atributo id o est\u00E1 mal digitado
+jsp.error.usebean.missing.type = useBean ({0})\: Se debe de especificar atributo class o type\:
+jsp.error.usebean.duplicate = useBean\: Nombre de bean duplicado\: {0}
+jsp.error.usebean.prohibited.as.session = No puedo usar como bean de sesi\u00F3n {0} ya que est\u00E1 prohibido por directiva jsp definida previamente\: 
+jsp.error.usebean.not.both = useBean\: No puede especificar ambos atributos class y beanName\: 
+jsp.error.usebean.bad.type.cast = useBean ({0})\: Tipo ({1}) no es asignable desde clase ({2}) 
+jsp.error.invalid.scope = Valor ilegal de atributo 'scope'\: {0} (debe de ser uno de "page", "request", "session", o "application")
+jsp.error.classname = No pude determinar el nombre de clase desde el fichero .class
+jsp.error.outputfolder = no hay carpeta de salida
+jsp.warning.bad.type = Aviso\: tipo incorrecto en archivo .class
+jsp.error.data.file.write = Error mientras escrib\u00EDa el archivo de datos
+jsp.error.page.conflict.contenttype = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'contentType' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.contenttype = Directiva Page\: valor incorrecto para contentType
+jsp.error.page.conflict.session = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'session' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.session = Directiva Page\: valor incorrecto para session
+jsp.error.page.conflict.buffer = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'buffer'con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.buffer = Directiva Page\: valor incorrecto para b\u00FAfer
+jsp.error.page.conflict.autoflush = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'autoFlush' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.autoflush = \=Directiva Page\: valor incorrecto para autoFlush
+jsp.error.page.conflict.isthreadsafe = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isThreadSafe' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.isthreadsafe = \=Directiva Page\: valor incorrecto para isThreadSafe
+jsp.error.page.conflict.info = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'info' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.info = \=Directiva Page\: valor incorrecto para info
+jsp.error.page.conflict.iserrorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isErrorPage' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.iserrorpage = \=Directiva Page\: valor incorrecto para isErrorPage
+jsp.error.page.conflict.errorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'errorPage' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.language = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'language' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.language = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de 'language' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.language.nonjava = Directiva Page\: atributo language incorrecto
+jsp.error.tag.language.nonjava = Directiva Tag\: atributo language incorrecto
+jsp.error.page.defafteruse.language = Directiva Page\: No puedo definir lenguage tras un scriptlet
+jsp.error.page.nomapping.language = Directiva Page\: No hay mapeado para language\: 
+jsp.error.page.conflict.extends = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'extends' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.iselignored = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isELIgnored' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.iselignored = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de 'isELIgnored' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.iselignored = Directiva Page\: valor inv\u00E1lido para isELIgnored
+jsp.error.tag.invalid.iselignored = Directiva Tag\: valor incorrecto para isELIgnored
+jsp.error.page.multi.pageencoding = La directiva Page no debe de tener m\u00FAltiples ocurrencias de pageencoding
+jsp.error.tag.conflict.attr = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias del atributo "{0}" con valores distintos (viejo\: {1}, nuevo\: {2})
+jsp.error.tag.multi.pageencoding = La directiva Tag no debe de tener m\u00FAltiples ocurrencias de pageencoding
+jsp.error.page.bad_b_and_a_combo = Directiva Page\: Combinaci\u00F3n ilegal de buffer\="none" y autoFlush\="false"
+jsp.error.not.impl.taglib = Error Interno\: Tag extensions no implementado
+jsp.error.include.missing.file = No tiene argumento de nombre de fichero 
+jsp.error.include.bad.file = Argumento de nombre de fichero no v\u00E1lido
+jsp.error.include.exception = No se puede incluir {0}
+jsp.error.stream.closed = Stream cerrado
+jsp.error.invalid.forward = Tag forward no v\u00E1lido
+jsp.error.unknownException = \u00A1Error no caturado\!. Deber\u00EDas de considerar el poner una p\u00E1gina de error para avisar de los errores m\u00E1s elegantemente
+jsp.error.invalid.directive = Directiva no v\u00E1lida
+jsp.error.invalid.implicit = TLD impl\u00EDcito inv\u00E1lido para fichero de marca en {0}
+jsp.error.invalid.implicit.version = Versi\u00F3n inv\u00E1lida de JSP definida en TLD impl\u00EDcito para fichero de marca en {0}
+jsp.error.invalid.version = Versi\u00F3n inv\u00E1lida de JSP definida para fichero de marca en {0}
+jsp.error.directive.istagfile = La Directiva {0} no puede usarse en archivo de tag
+jsp.error.directive.isnottagfile = La Directiva {0} s\u00F3lo se puede usar en un archivo de tag
+jsp.error.tagfile.tld.name = El atributo "name" de la directiva tag tiene un valor {0} mientras que el tag "name" del elemento "tag-file" en el TLD es {1}
+jsp.error.action.istagfile = La acci\u00F3n {0} no se puede usar en un archivo tag
+jsp.error.action.isnottagfile = La acci\u00F3n {0} s\u00F3lo se puede usar en archivos tag
+jsp.error.unterminated = Tag {0} no terminado
+jsp.error.usebean.notinsamefile = El Tag useBean debe de empezar y terminar en el mismo archivo f\u00EDsico
+jsp.error.loadclass.taghandler = No se puede cargar la clase {0}
+jsp.error.unable.compile = No se puede compilar la clase para JSP
+jsp.error.unable.load = No se puede cargar la clase para JSP
+jsp.error.unable.rename = No se puede renombrar el archivo de clase {0} a {1}
+jsp.error.mandatory.attribute = {0}\: Falta atributo obligatorio {1}
+jsp.error.flush = Excepci\u00F3n sucedida al vaciar los datos
+jsp.engine.info = Motor Jasper JSP 2.2
+jsp.error.invalid.expression = "{0}" contiene expresiones incorrectas\: {1}
+jsp.error.invalid.attribute = {0}\: Atributo incorrecto, {1}
+jsp.error.usebean.class.notfound = Clase\: {0} no hallada
+jsp.error.file.cannot.read = No se puede leer el archivo\: {0}
+jsp.error.file.already.registered = El archivo {0} ya se ha visto, \u00BFpodr\u00EDa ser un include recursivo?
+jsp.error.file.not.registered = Archivo {0} not visto en include
+jsp.error.quotes.unterminated = Comillas no terminadas
+jsp.error.attr.quoted = El valor del atributo deber\u00EDa ir entre comillas
+jsp.error.attr.novalue = Atributo {0} no tiene valor
+jsp.error.tag.attr.unterminated = Lista de atributos del tag no terminada
+jsp.error.param.noname = No hay nombre en el tag PARAM
+jsp.error.param.novalue = No hay valor en el tag PARAM
+jsp.error.beans.nullbean = Se ha intentado una operaci\u00F3n de bean en un objeto nulo
+jsp.error.beans.nobeaninfo = No se puede encontrar BeanInfo para el bean  ''{0}'' seguramente la clase no existe
+jsp.error.beans.introspection = Una excepci\u00F3n ha tenido lugar mientras se le\u00EDa el m\u00E9todo de lectura de la propiedad ''{0}'' en un bean del tipo ''{1}''\:\n\
+    {2}
+jsp.error.beans.nomethod = No puedo encontrar un m\u00E9todo para leer la propiedad ''{0}'' en un bean del tipo ''{1}''
+jsp.error.beans.nomethod.setproperty = No puedo encontrar un m\u00E9todo para escribir la propiedad ''{0}'' en un bean del tipo ''{2}''
+jsp.error.beans.noproperty = No puedo encontrar informaci\u00F3n de la propiedad ''{0}'' en un bean del tipo ''{1}''
+jsp.error.beans.property.conversion = No puedo convertir cadena "{0}" a clase "{1}" para atributo "{2}"\: {3}
+jsp.error.beans.propertyeditor.notregistered = Editor de Propiedades no registrado con el PropertyEditorManager
+jsp.error.beans.setproperty.noindexset = No puedo poner la propiedad indexada
+jsp.error.include.tag = Tag jsp\:include no v\u00E1lido
+jsp.error.include.noflush = jsp\:include necesita tener "flush\=true"
+jsp.error.include.badflush = jsp\:include page\="..." flush\="true" es la \u00FAnica combinaci\u00F3n v\u00E1lida en JSP 1.0
+jsp.error.attempt_to_clear_flushed_buffer = Error\: Se ha intentado limpiar un buffer que ya hab\u00EDa sido escrito
+jsp.error.overflow = Error\:Buffer de JSP desbordado
+jsp.error.paramexpected = El tag "param" era esperado con los atributos "name" y "value" despu\u00E9s del tag "params".
+jsp.error.param.invalidUse = La acci\u00F3n jsp\:param no debe de ser usada fuera de los elementos jsp\:include, jsp\:forward o jsp\:params
+jsp.error.params.invalidUse = jsp\:params debe de ser un hijo directo de jsp\:plugin
+jsp.error.fallback.invalidUse = jsp\:fallback debe de ser un hijo directo de jsp\:plugin
+jsp.error.namedAttribute.invalidUse = jsp\:attribute debe de ser el subelemento de una acci\u00F3n est\u00E1ndar o de cliente
+jsp.error.jspbody.invalidUse = jsp\:body debe de ser el subelemento de una acci\u00F3n est\u00E1ndar o de cliente
+jsp.error.closeindividualparam = El tag param necesita ser cerrado con "/>"
+jsp.error.closeparams = El tag param necesita ser cerrado con /params
+jsp.error.params.emptyBody = jsp\:params debe de contener al menos un jsp\:param anidado
+jsp.error.params.illegalChild = jsp\:params no debe de contener elementos anidados que no sean jsp\:param
+jsp.error.plugin.notype = Tipo no declarado en jsp\:plugin
+jsp.error.plugin.badtype = Valor ilegal para atributo 'type' en jsp\:plugin\: debe de ser 'bean' o 'applet'
+jsp.error.plugin.nocode = C\u00F3digo no declarado en jsp\:plugin
+jsp.error.ise_on_clear = Es ilegal usar clear() cuando el tama\u00F1o del buffer es cero
+jsp.error.setproperty.beanNotFound = setProperty\: Bean {0} no encontrado
+jsp.error.getproperty.beanNotFound = getProperty\: Bean {0} no encontrado
+jsp.error.setproperty.ClassNotFound = setProperty\: clase {0} no encontrada
+jsp.error.javac = Excepci\u00F3n de Javac
+jsp.error.javac.env = Entorno
+jsp.error.compilation = Error compilando fichero\: {0} {1}
+jsp.error.setproperty.invalidSyntax = setproperty\: no puedo tener valor no nulo si la propiedad\=*
+jsp.error.setproperty.beanInfoNotFound = setproperty\: beanInfo para bean {0} no encontrado
+jsp.error.setproperty.paramOrValue = setProperty\: O param o value pueden estar presentes
+jsp.error.setproperty.arrayVal = setProperty\: No puede escribir en la propiedad de array {0} a trav\u00E9s de una valor de cadena literal
+jsp.warning.keepgen = Aviso\: valor incorrecto para el initParam keepgen. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.xpoweredBy = Aviso\: valor incorrecto para el initParam xpoweredBy. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.enablePooling = Aviso\: valor incorrecto para el initParam enablePooling. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.invalidTagPoolSize = Aviso\: valor incorrecto para el par\u00E1metro init llamado tagPoolSize. Se usar\u00E1 la medida por defecto de {0}
+jsp.warning.mappedFile = Aviso\: valor incorrecto para el initParam mappedFile. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.classDebugInfo = Aviso\: valor incorrecto para el initParam classdebuginfo. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.checkInterval = Aviso\: valor incorrecto para el initParam checkInterval. Se usar\u00E1 el valor por defecto de "300" segundos
+jsp.warning.modificationTestInterval = Aviso\: valor incorrecto para el initParam modificationTestInterval. Se usar\u00E1 el valor por defecto de "4" segundos
+jsp.warning.development = Aviso\: valor incorrecto para el initParam development. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.fork = Aviso\: valor incorrecto para el initParam fork. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.reloading = Aviso\: valor incorrecto para el initParam reloading. Se usar\u00E1 el valor por defecto de "true"
+jsp.warning.dumpSmap = Aviso\: valor incorrecto para el initParam dumpSmap. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.genchararray = Aviso\: valor incorrecto para el initParam genStrAsCharArray. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.suppressSmap = Aviso\: valor incorrecto para el initParam suppressSmap. Se usar\u00E1 el valor por defecto de "false"
+jsp.warning.displaySourceFragment = Aviso\: valor incorrecto para el initParam displaySourceFragment. Se usar\u00E1 el valor por defecto de "verdadero"
+jsp.error.badtaglib = No se puede abrir la biblioteca de tags {0}\: {1}
+jsp.error.badGetReader = No se puede crear un reader cuando el stream no tiene buffer
+jsp.warning.unknown.element.in.taglib = Elemento desconocido ({0}) en taglib
+jsp.warning.unknown.element.in.tag = Elemento desconocido ({0}) en tag
+jsp.warning.unknown.element.in.tagfile = Elemento desconocido ({0}) en tag-file
+jsp.warning.unknown.element.in.attribute = Elemento desconocido ({0}) en attribute
+jsp.warning.unknown.element.in.variable = Elemento desconocido ({0}) en variable
+jsp.warning.unknown.element.in.validator = Elemento desconocido ({0}) en validator
+jsp.warning.unknown.element.in.initParam = Elemento desconocido ({0}) en init-param de validator
+jsp.warning.unknown.element.in.function = Elemento desconocido ({0}) en function
+jsp.error.more.than.one.taglib = M\u00E1s de una biblioteca de tags en el TLD\: {0}
+jsp.error.teiclass.instantiation = No se puede cargar la clase TagExtraInfo llamada\: {0}
+jsp.error.non_null_tei_and_var_subelems = Tag {0} tiene uno o m\u00E1s subelementos variable y una clase TagExtraInfo que devuelve una o m\u00E1s VariableInfo
+jsp.error.parse.error.in.TLD = Error de an\u00E1lisis en el descriptor de biblioteca de tags\: {0}
+jsp.error.unable.to.open.TLD = No se puede abrir el descriptor de biblioteca de tags\: {0}
+jsp.buffer.size.zero = Tama\u00F1o de buffer <\= 0
+jsp.error.file.not.found = Archivo JSP "{0}" no encontrado
+jsp.message.copyinguri = Copiando {0} en {1}
+jsp.message.htmlcomment = \n\
+    Quitando comentario\: \t{0}
+jsp.message.handling_directive = \n\
+    Resolviendo directiva\: {0}\t{1}
+jsp.message.handling_plugin = \n\
+    Plugin\: {0}
+jsp.message.package_name_is = El Nombre del Package es\: {0}
+jsp.message.class_name_is = El Nombre de la clase es\: {0}
+jsp.message.java_file_name_is = El Nombre del Archivo Java es\: {0}
+jsp.message.class_file_name_is = El Nombre del Archivo de clase es\: {0}
+jsp.message.accepted = Acept\u00F3 {0} en {1}
+jsp.message.adding_jar = A\u00F1adiendo jar {0} a mi classpath
+jsp.message.compiling_with = Compilado con\: {0}
+jsp.message.template_text = texto plantilla
+jsp.error.missing_attribute = De acuerdo con el TLD el atributo {0} es obligatorio para el tag {1}
+jsp.error.bad_attribute = El atributo {0} no es v\u00E1lido seg\u00FAn el TLD especificado
+jsp.error.tld.unable_to_read = Imposible de leer TLD "{1}" desde archivo JAR "{0}"\: {2}
+jsp.error.tld.unable_to_get_jar = Imposible obtener recurso JAR "{0}" conteniendo TLD\: {1}
+jsp.error.tld.missing_jar = Falta recurso JAR "{0}" conteniendo TLD
+jsp.error.webxml_not_found = No puedo localizar web.xml
+jsp.cmd_line.usage = Uso\: jsptoservlet [-dd <ruta/a/DirectorioSalida>] [-keepgenerated] <Archivos .jsp>
+jsp.message.cp_is = Classpath {0} es\: {1}
+jsp.error.unable.to_load_taghandler_class = No se puede cargar clase manejadora {0} del tag a causa de {1}
+jsp.error.unable.to_find_method = No se puede encontrar el m\u00E9todo de escritura para el atributo\: {0}
+jsp.error.unable.to_convert_string = No pude convertir un String a {0} para atributo {1}
+jsp.error.unable.to_introspect = No se puede hacer introspecci\u00F3n en manejador de tag clase\: {0} a causa de {1}
+jsp.error.bad_tag = No existe el tag {0} en la biblioteca importada con prefijo {1}
+jsp.error.xml.bad_tag = No se ha definido el tag "{0}" en la biblioteca tag asociada con uri "{1}"
+jsp.error.bad_string_Character = No puede extraer un Character desde un array de tama\u00F1o cero
+jsp.error.bad_string_char = No puede extraer un char desde un array de tama\u00F1o cero
+jsp.warning.compiler.class.cantcreate = No puedo crear una instancia de la clase especificada {0} de plugin del compilador debido a {1}. Se usar\u00E1 el compilador Java de Sun.
+jsp.warning.compiler.class.notfound = No puedo encontrar una instancia de la clase {0} de plugin de compilador. Se usar\u00E1 el compilador del Java de Sun.
+jsp.warning.compiler.path.notfound = Trayectoria del compilador especificado {0} no encontrada. Se usar\u00E1 el PATH del sistema.
+jsp.error.jspc.uriroot_not_dir = La opci\u00F3n -uriroot debe de especificar un directorio ya existente
+jsp.error.jspc.missingTarget = Falta target\: Debe de especificar -webapp o -uriroot o una o m\u00E1s p\u00E1ginas JSP
+jsp.error.jspc.no_uriroot = No se ha especificado uriroot y no puede ser localizado en los archivos JSP especificados
+jspc.implicit.uriRoot = uriRoot implicitamente puesto a "{0}"
+jspc.usage = Uso\: jspc <opciones> [--] <Archivos JSP>\n\
+    donde <Archivos JSP> son\:\n\
+    \    -webapp <dir>      Un directorio conteniendo una web-app. Todas las\n\
+    \                       p\u00E1ginas jsp ser\u00E1n compiladas recursivamente\n\
+    o cualquier n\u00FAmero de\n\
+    \    <Archivo>          Un Archivo para ser interpretado como una p\u00E1gina jsp\n\
+    y donde <opciones> incluyen\:\n\
+    \    -help              Muestra este mensaje de ayuda\n\
+    \    -v                 Modo detallado\n\
+    \    -d <dir>           Directorio de salida\n\
+    \    -l                 Muestra el nombre de la p\u00E1gina JSP al ocurrir un fallo\n\
+    \    -s                 Muestra el nombre de la p\u00E1gina JSP al tener \u00E9xito\n\
+    \    -p <name>          Nombre del package objetivo\n\
+    \                       (por defecto org.apache.jsp)\n\
+    \    -c <name>          Nombre de la clase objetivo\n\
+    \                       (s\u00F3lo se aplica a la primera p\u00E1gina JSP)\n\
+    \    -mapped            Genera llamadas separadas a write() para cada l\u00EDnea de\n\
+    \                       HTML en el JSP\n\
+    \    -die[\#]            Genera un c\u00F3digo de retorno de error (\#) en errores\n\
+    \                       fatales. (por defecto 1).\n\
+    \    -uribase <dir>     El directorio uri de donde deben de partir las\n\
+    \                       compilaciones. (por defecto "/")\n\
+    \    -uriroot <dir>     Igual que -webapp\n\
+    \    -compile           Compila los servlets generados\n\
+    \    -webinc <file>     Crea unos mapeos parciales de servlet en el archivo\n\
+    \    -webxml <file>     Crea un web.xml completo en el archivo.\n\
+    \    -ieplugin <clsid>  Java Plugin classid para Internet Explorer\n\
+    \    -classpath <path>  Pasa por alto la propiedad de sistema java.class.path\n\
+    \    -xpoweredBy        A\u00F1ade cabecera de respuesta  X-Powered-By\n\
+    \    -trimSpaces        Trim spaces in template text between actions, directives\n\
+    \    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+    \    -source <version>   Set the -source argument to the compiler (default 1.6)\n\
+    \    -target <version>   Set the -target argument to the compiler (default 1.6)\n
+jspc.webxml.header = <?xml version\="1.0" encoding\="ISO-8859-1"?>\n\
+    \n\
+    <\!DOCTYPE web-app\n\
+    \    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+    \    "http\://java.sun.com/dtd/web-app_2_3.dtd">\n\
+    <\!--\n\
+    Creado automaticamente mediante Apache Tomcat JspC.\n\
+    -->\n\
+    <web-app>\n\
+    \n
+jspc.webxml.footer = \n\
+    </web-app>\n\
+    \n
+jspc.webinc.header = \n\
+    <\!--\n\
+    Creado automaticamente mediante Apache Tomcat JspC.\n\
+    Coloque este fragmento en el fichero web.xml antes de \n\
+    todos los elementos\: icon, display-name, description, \n\
+    distributable y context-param .\n\
+    -->\n
+jspc.webinc.footer = \n\
+    <\!--\n\
+    Los Elementos\: session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+    resource-ref, security-constraint, login-config, security-role,\n\
+    env-entry y ejb-ref deber\u00E1n de ir despu\u00E9s de este fragmento .\n\
+    -->\n
+jspc.webinc.insertEnd = <\!-- Fin de mapeos de servlet JSPC -->
+jspc.webinc.insertStart = <\!-- Inicio de mapeos de servlet JSPC -->
+jspc.error.jasperException = error-el archivo ''{0}'' ha generado la excepci\u00F3n de sint\u00E1xis siguiente\: {1}
+jspc.error.generalException = ERROR-el archivo ''{0}'' ha generado la excepci\u00F3n general siguiente\:
+jspc.error.fileDoesNotExist = El archivo ''{0}'' utilizado como argumento no existe.
+jspc.error.emptyWebApp = -webapp necesita un argumento de archivo
+jsp.error.library.invalid = La p\u00E1gina JSP es incorrecta de acuerdo a la biblioteca {0}\: {1}
+jsp.error.tlvclass.instantiation = No pude cargar o instanciar clase TagLibraryValidator\: {0}
+jsp.error.tlv.invalid.page = Mensajes de error de validaci\u00F3n desde TagLibraryValidator para {0} in {1}
+jsp.error.tei.invalid.attributes = Mensajes de error de validaci\u00F3n desde TagExtraInfo para {0}
+jsp.parser.sax.propertynotsupported = Propiedad SAX no soportada\: {0}
+jsp.parser.sax.propertynotrecognized = Propiedad SAX no reconocida\: {0}
+jsp.parser.sax.featurenotsupported = Caracter\u00EDstica SAX no soportada\: {0}
+jsp.parser.sax.featurenotrecognized = Caracter\u00EDstica SAX no reconocida\: {0}
+jsp.error.no.more.content = Alcanzado fin de contenido mietras se requer\u00EDa m\u00E1s an\u00E1lisis\: \u00BFerror de anidamiento de tag?
+jsp.error.parse.xml = Error de an\u00E1lisis XML en archivo {0}
+jsp.error.parse.xml.line = Error de an\u00E1lisis XML en archivo {0}\: (l\u00EDnea {1}, col {2})
+jsp.error.parse.xml.scripting.invalid.body = El cuerpo de elemento {0} no debe de contener elementos XML
+jsp.error.internal.tldinit = No pude inicializar TldLocationsCache\: {0}
+jsp.error.internal.filenotfound = Error Interno\: Archivo {0} no hallado
+jsp.error.internal.evaluator_not_found = Error interno\: no pude cargar evaluador de expresiones
+jsp.error.parse.xml.invalidPublicId = PUBLIC ID incorrecta\: {0}
+jsp.error.include.flush.invalid.value = Valor incorrecto para atributo flush\: {0}
+jsp.error.unsupported.encoding = Codificaci\u00F3n no soportada\: {0}
+tld.error.variableNotAllowed = Es un error para un tag, que tiene uno o m\u00E1s subelementos variables, el tener una clase TagExtraInfo que devuelve un objeto no nulo.
+jsp.error.tldInWebDotXmlNotFound = No pude localizar TLD {1} para URI {0} especificado en web.xml
+jsp.error.taglibDirective.absUriCannotBeResolved = La uri absoluta\: {0} no puede resolverse o en web.xml o el los archivos jar desplegados con esta aplicaci\u00F3n
+jsp.error.taglibDirective.missing.location = No se ha especificado ni el atributo 'uri' ni el 'tagdir'
+jsp.error.taglibDirective.both_uri_and_tagdir = Se han especificado ambos atributos 'uri' y 'tagdir'
+jsp.error.invalid.tagdir = El directorio de archivo Tag {0} no comienza con "/WEB-INF/tags"
+jsp.error.unterminated.user.tag = Tag definido por usuario no terminado\: tag final {0} no hallado o anidado incorrectamente
+#jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata = Error de Validaci\u00F3n\: El Elemento &lt;{0}&gt; no puede tener datos plantilla. Los datos plantilla deben de estar encapsulados dentro de un elemento &lt;jsp\:text&gt;. [JSP1.2 PFD secci\u00F3n 5.1.9]\n\
+    Datos de Plantilla en error\: {1}
+#Error while processing taglib jar file {0}: {1}
+jsp.error.taglib.reserved.prefix = El prefijo taglib {0} est\u00E1 reservado
+jsp.error.invalid.javaEncoding = Codificaciones java incorrectas. Intent\u00E9 {0} y luego {1}. Ambas fallaron.
+jsp.error.needAlternateJavaEncoding = La codificaci\u00F3n java por defecto {0} es incorrecta en tu plataforma java. Se puede especificar una alternativa v\u00EDa par\u00E1metro 'javaEncoding' de JspServlet.
+#Error when compiling, used for jsp line number error messages
+jsp.error.single.line.number = Ha tenido lugar un error en la l\u00EDnea\: {0} en el archivo jsp\: {1}
+jsp.error.multiple.line.number = \n\
+    \n\
+    Ha tenido lugar un error entre las l\u00EDneas\: {0} y {1} en el archivo jsp\: {2}\n\
+    \n
+jsp.error.java.line.number = Ha tenido lugar un error en la l\u00EDnea\: {0} en el fichero java generado
+jsp.error.corresponding.servlet = Error de servlet generado\:\n
+jsp.error.empty.body.not.allowed = Cuerpo vac\u00EDo no permitido para {0}
+jsp.error.jspbody.required = Se debe de usar jsp\:body para especificar cuerpo tag para {0} si se usa jsp\:attribute.
+jsp.error.jspbody.emptybody.only = El tag {0} s\u00F3lo puede tener jsp\:attribute en su cuerpo.
+jsp.error.no.scriptlets = Los elementos de Scripting (&lt;%\!, &lt;jsp\:declaration, &lt;%\=, &lt;jsp\:expression, &lt;%, &lt;jsp\:scriptlet ) no est\u00E1n permitidos aqu\u00ED.
+jsp.error.internal.unexpected_node_type = Error Interno\: Encontrado tipo de nodo inesperado
+jsp.error.tld.fn.invalid.signature = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}
+jsp.error.tld.fn.duplicate.name = Nombre duplicado de funci\u00F3n {0} en biblioteca de tag {1}
+jsp.error.tld.fn.invalid.signature.commaexpected = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Se esperaba Coma ','. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Se esperaba Par\u00E9ntesis '('. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}.
+jsp.error.tld.mandatory.element.missing = Falta o est\u00E1 vac\u00EDo elemento TLD obligatorio\: {0}
+jsp.error.dynamic.attributes.not.implemented = El tag {0} declara que acepta atributos din\u00E1micos pero no implementa la interfaz requerida
+jsp.error.nomatching.fragment = No puedo hallar una directiva de atributo (con name\={0} y fragment\=true) antes de la directiva de fragment.
+jsp.error.attribute.noequal = se esperaba s\u00EDmbolo igual
+jsp.error.attribute.noquote = se esperaba s\u00EDmbolo comillas
+jsp.error.attribute.unterminated = el atributo para {0} no est\u00E1 terminado correctamente
+jsp.error.attribute.noescape = El valor de atributo {0} est\u00E1 entrecomillado con {1} que debe de usar escape al usarse dentro del valor
+jsp.error.missing.tagInfo = El objeto TagInfo para {0} falta del TLD
+jsp.error.deferredmethodsignaturewithoutdeferredmethod = No puedo especificar firma de m\u00E9todo si 'deferredMethod' no es 'verdadero'
+jsp.error.deferredvaluetypewithoutdeferredvalue = No puedo especificar un tipo de valor si 'deferredValue' no es 'verdadero'
+jsp.error.deferredmethodandvalue = 'deferredValue' y 'deferredMethod' no pueden ser ambos 'verdadero'
+jsp.error.fragmentwithtype = No puede especificar ambos atributos 'fragment' y 'type'. Si est\u00E1 presente 'fragment', 'type' se pone como 'javax.servlet.jsp.tagext.JspFragment'
+jsp.error.fragmentwithrtexprvalue = No puede especificar ambos atributos 'fragment' y 'rtexprvalue'.  Si est\u00E1 presente 'fragment', 'rtexprvalue' se pone como 'true'
+jsp.error.fragmentWithDeclareOrScope = Ambos atributos 'fragment' y 'declare' o 'scope' se han especificado en la directiva variable
+jsp.error.var_and_varReader = S\u00F3lo se puede especificar uno de 'var' o 'varReader'
+jsp.error.missing_var_or_varReader = Falta atributo 'var' o 'varReader'
+jsp.warning.bad.urlpattern.propertygroup = Valor malo {0} en el subelemento url-pattern en web.xml
+jsp.error.unknown_attribute_type = Tipo de atributo desconocido ({1}) para atributo {0}.
+jsp.error.coerce_to_type = No puedo coaccionar el valor ({2}) a tipo ({1}) para atributo {0}.
+jsp.error.jspelement.missing.name = Falta atributo obligatorio XML-style 'name'
+jsp.error.xmlns.redefinition.notimplemented = Error interno\: Intento de redefinir xmlns\:{0}. La redefinici\u00F3n de espacios de nombre no est\u00E1 implementada.
+jsp.error.could.not.add.taglibraries = No pude a\u00F1adir una o m\u00E1s bibliotecas.
+jsp.error.duplicate.name.jspattribute = El atributo {0} especificado en la acci\u00F3n standard o custom tambi\u00E9n aparece como el valor del atributo name en jsp\:attribute
+jsp.error.not.in.template = {0} no permitido en una plantilla cuerpo de texto.
+jsp.error.badStandardAction = Acci\u00F3n est\u00E1ndar incorrecta
+jsp.error.xml.badStandardAction = Acci\u00F3n est\u00E1ndar incorrecta\: {0}
+jsp.error.tagdirective.badbodycontent = body-content incorrecto ({0}) en directiva tag
+jsp.error.simpletag.badbodycontent = El TLD para la clase {0} especifica un body-content es incorrecto (JSP) para un SimpleTag.
+jsp.error.config_pagedir_encoding_mismatch = El Page-encoding especificado en jsp-property-group ({0}) es diferente del especificado en la diectiva page ({1})
+jsp.error.prolog_pagedir_encoding_mismatch = El Page-encoding especificado en XML prolog ({0}) difiere del especificado en la directiva page ({1})
+jsp.error.prolog_config_encoding_mismatch = El Page-encoding especificado en XML prolog ({0}) difiere del especificado en jsp-property-group ({1})
+jsp.error.attribute.custom.non_rt_with_expr = Seg\u00FAn el TLD o la directiva attribute del archivo tag, el atributo {0} no acepta expresiones
+jsp.error.attribute.standard.non_rt_with_expr = El atributo {0} de la acci\u00F3n est\u00E1ndar {1} no acepta expresiones
+jsp.error.scripting.variable.missing_name = Imposible determinar nombre de variable de scripting desde atributo {0}
+jasper.error.emptybodycontent.nonempty = Seg\u00FAn el TLD, el tag {0} debe de estar vac\u00EDo, pero no lo est\u00E1
+jsp.error.tagfile.nameNotUnique = El valor de {0} y el valor de {1} en la l\u00EDnea {2} son el mismo.
+jsp.error.tagfile.nameFrom.noAttribute = No puedo hallar una directiva attribute con un atributo name con un valor "{0}", el valor de este atributo name-from-attribute.
+jsp.error.tagfile.nameFrom.badAttribute = La directiva attribute (declarada en la l\u00EDnea {1} y cuyo atributo name es "{0}", el valor de este atributo name-from-attribute attribute) debe de ser del tipo java.lang.String, es "required" y no un "rtexprvalue".
+jsp.error.page.noSession = No puedo acceder al \u00E1mbito de sesi\u00F3n en una p\u00E1gina que no participa en una sesi\u00F3n
+jsp.error.usebean.noSession = Es ilegal para useBean el usar \u00E1mbito de sesi\u00F3n cuando la p\u00E1gina JSP declara (v\u00EDa directiva de p\u00E1gina) que no participa en sesiones
+jsp.error.xml.encodingByteOrderUnsupported = El orden de byte dado para encoding "{0}" no est\u00E1 soportado
+jsp.error.xml.encodingDeclInvalid = Nombre de codificaci\u00F3n "{0}" incorrecto.
+jsp.error.xml.encodingDeclRequired = Se necesita la declaraci\u00F3n encoding en la declaraci\u00F3n de texto
+jsp.error.xml.morePseudoAttributes = se esperan m\u00E1s pseudo-atributos
+jsp.error.xml.noMorePseudoAttributes = no se permiten m\u00E1s pseudo-atributos.
+jsp.error.xml.versionInfoRequired = Se requiere la versi\u00F3n en la declaraci\u00F3n XML.
+jsp.error.xml.xmlDeclUnterminated = La declaraci\u00F3n XML debe de terminar con "?>".
+jsp.error.xml.reservedPITarget = La instrucci\u00F3n de procesamiento que coincide con "[xX][mM][lL]" no est\u00E1 permitida.
+jsp.error.xml.spaceRequiredInPI = Se necesita un espacio en blanco entre la instrucci\u00F3n de procesamiento y los datos.
+jsp.error.xml.invalidCharInContent = Un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) se hall\u00F3 en el contenido del elemento del documento.
+jsp.error.xml.spaceRequiredBeforeStandalone = Se necesita un espacio en blanco antes del pseudo-atributo encoding en la declaraci\u00F3n XML.
+jsp.error.xml.sdDeclInvalid = El valor de declaraci\u00F3n de documento standalone debe de ser "yes" o "no", no "{0}".
+jsp.error.xml.invalidCharInPI = Se hall\u00F3 un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) en la instrucci\u00F3n de procesamiento
+jsp.error.xml.versionNotSupported = No se soporta la versi\u00F3n XML "{0}", s\u00F3lo se soporta XML 1.0
+jsp.error.xml.pseudoAttrNameExpected = se esperaba un pseudo-atributo name.
+jsp.error.xml.expectedByte = Se esperaba byte {0} de {1}-byte de secuencia UTF-8.
+jsp.error.xml.invalidByte = Incorrecto byte {0} de {1}-byte de secuencia UTF-8.
+jsp.error.xml.operationNotSupported = La operaci\u00F3n "{0}" no est\u00E1 soportada por lector {1}.
+jsp.error.xml.invalidHighSurrogate = Los bits de surrogaci\u00F3n alta en secuencai UTF-8 no deben de exceder 0x10 pero se hall\u00F3 0x{0}.
+jsp.error.xml.invalidASCII = El Byte "{0}" no es ASCII de 7-bit.
+jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl = Se necesita espacio en blanco antes del pseudo-atributo encoding en la declaraci\u00F3n XML.
+jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl = Se necesita espacio en blanco antes del pseudo-atributo encoding en la declaraci\u00F3n text.
+jsp.error.xml.spaceRequiredBeforeVersionInTextDecl = Se necesita espacio en blanco antes del pseudo-atributo version en la declaraci\u00F3n text.
+jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl = Se necesita espacio en blanco antes del pseudo-atributo version en la declaraci\u00F3n XML.
+jsp.error.xml.eqRequiredInXMLDecl = El car\u00E1cter '' \= '' debe de serguir a "{0}" en la declaraci\u00F3n XML.
+jsp.error.xml.eqRequiredInTextDecl = El car\u00E1cter '' \= '' debe de serguir a "{0}" en la declaraci\u00F3n text.
+jsp.error.xml.quoteRequiredInTextDecl = El valor que sigue a "{0}" en la declaraci\u00F3n text debe de ser una cadena entre comillas.
+jsp.error.xml.quoteRequiredInXMLDecl = El valor que sigue a "{0}" en la declaraci\u00F3n XML debe de ser un cadena entre comillas.
+jsp.error.xml.invalidCharInTextDecl = Un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) se hall\u00F3 en la declaraci\u00F3n text
+jsp.error.xml.invalidCharInXMLDecl = Un car\u00E1cter XML incorrecto (Unicode\: 0x{0}) se hall\u00F3 en la declaraci\u00F3n XML
+jsp.error.xml.closeQuoteMissingInTextDecl = Faltan las comillas de cierre en el valor que sigue a "{0}" en la declaraci\u00F3n text.
+jsp.error.xml.closeQuoteMissingInXMLDecl = Faltan las comillas de cierre en el valor que sigue a  "{0}" en la declaraci\u00F3n XML.
+jsp.error.multiple.jsp = No puedo tener m\u00FAltiples especificaciones de
+jsp.error.jspoutput.conflict = &lt;jsp\:output&gt;\: ilegal tener ocurrencias m\u00FAltiples de "{0}" con diferentes valores (viejo\: {1}, nuevo\: {2})
+jsp.error.jspoutput.doctypenamesystem = &lt;jsp\:output&gt;\: atributos 'doctype-root-element' y 'doctype-system' deben de aparecer juntos
+jsp.error.jspoutput.doctypepulicsystem = &lt;jsp\:output&gt;\: atributo 'doctype-system' debe de aparecer si aparece atributo 'doctype-public'
+jsp.error.jspoutput.nonemptybody = &lt;jsp\:output&gt; no debe de tener un cuerpo
+jsp.error.jspoutput.invalidUse = &lt;jsp\:output&gt; no se debe de usar en sint\u00E1xis est\u00E1ndar
+jsp.error.attributes.not.allowed = {0} no debe de tener atributos
+jsp.error.tagfile.badSuffix = Falta sufijo ".tag" en trayectoria de archivo de tag {0}
+jsp.error.tagfile.illegalPath = Trayectoria de archivo de tag\: {0}, debe de comenzar con "/WEB-INF/tags" o "/META-INF/tags"
+jsp.error.plugin.wrongRootElement = El nombre del elemento ra\u00EDz en {0} difiere de {1}
+jsp.error.attribute.invalidPrefix = El prefijo de atributo {0} no se correponde con ninguna biblioteca importada
+jsp.error.nested.jspattribute = Una acci\u00F3n est\u00E1ndar jsp\:attribute no puede estar anidada dentro de otra acci\u00F3n est\u00E1ndar jsp\:attribute
+jsp.error.nested.jspbody = Una acci\u00F3n est\u00E1ndar jsp\:body no puede estar anidada dentro de otra acci\u00F3n est\u00E1ndar jsp\:body o jsp\:attribute
+jsp.error.variable.either.name = O el atributo name-given o name-from-attribute deben de ser especificados en una directiva variable
+jsp.error.variable.both.name = No se puede especificar ambos atributos name-given o name-from-attribute en una directiva variable
+jsp.error.variable.alias = Ambos atributos o ninguno de name-from-attribute y alias pueden ser especificados en una directiva variable
+jsp.error.attribute.null_name = Nombre de atributo nulo
+jsp.error.jsptext.badcontent = '&lt;', cuando aparece en el cuerpo de &lt;jsp\:text&gt;, debe de estar encapsulado dentro de un CDATA
+jsp.error.jsproot.version.invalid = N\u00FAmero incorrecto de versi\u00F3n\: "{0}", debe de ser "1.2" o "2.0" o "2.1" o "2.2"
+jsp.error.noFunctionPrefix = La funci\u00F3n {0} debe de usarse con un prefijo cuando no se especifica un espacio de nombres por defecto
+jsp.error.noFunction = La funci\u00F3n {0} no puede ser localizada mediante el prefijo especificado
+jsp.error.noFunctionMethod = El m\u00E9todo "{0}" para la funci\u00F3n "{1}" no se pudo hallar en la clase "{2}"
+jsp.error.function.classnotfound = La clase {0} especificada en el TLD para la funci\u00F3n {1} no se puede hallar\: {2}
+jsp.error.signature.classnotfound = La clase {0} especificada en la firma del m\u00E9todo en el TLD para la funci\u00F3n {1} no se puede hallar. {2}
+jsp.error.text.has_subelement = &lt;jsp\:text&gt; no debe de tener subelementos
+jsp.error.data.file.read = Error leyendo archivo "{0}"
+jsp.error.prefix.refined = Intento de redefinir el prefijo {0} por {1}, cuando ya estaba definido como {2} en el \u00E1mbito en curso.
+jsp.error.nested_jsproot = &lt;jsp\:root&gt; anidado
+jsp.error.unbalanced.endtag = El tgag final "&lt;/{0}" est\u00E1 desequilibrado
+jsp.error.invalid.bean = El valor el atributo de clsae useBean {0} es inv\u00E1lido.
+jsp.error.prefix.use_before_dcl = El prefijo {0} especificado en esta directiva de marca ha sido usado previamente mediante un fichero de acci\u00F3n {1} l\u00EDnea {2}.
+jsp.exception = Ha sucedido una excepci\u00F3n al procesar la p\u00E1gina JSP {0} en l\u00EDnea {1}
+jsp.error.el.template.deferred = \#{..} no est\u00E1 permitido en texto de plantilla
+jsp.error.el.parse = {0} \: {1}
+jsp.error.page.invalid.deferredsyntaxallowedasliteral = Directiva de p\u00E1gina\: valor inv\u00E1lido para deferredSyntaxAllowedAsLiteral
+jsp.error.tag.invalid.deferredsyntaxallowedasliteral = Directiva de marca\: valor inv\u00E1lido para deferredSyntaxAllowedAsLiteral
+jsp.error.page.conflict.deferredsyntaxallowedasliteral = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de 'deferredSyntaxAllowedAsLiteral' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.deferredsyntaxallowedasliteral = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de 'deferredSyntaxAllowedAsLiteral' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.page.invalid.trimdirectivewhitespaces = Directiva de p\u00E1gina\: valor inv\u00E1lido para trimDirectiveWhitespaces
+jsp.error.tag.invalid.trimdirectivewhitespaces = Directiva de marca\: valor inv\u00E1lido para trimDirectiveWhitespaces
+jsp.error.page.conflict.trimdirectivewhitespaces = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de 'trimDirectivewhitespaces' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.trimdirectivewhitespaces = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de 'trimDirectivewhitespaces' con diferentes valores (viejo\: {0}, nuevo\: {1})
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_fr.properties
new file mode 100644
index 0000000..2f66db6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_fr.properties
@@ -0,0 +1,316 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# $Id: LocalStrings_fr.properties,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+#
+# Default localized string information
+# Localized this the Default Locale as is fr_FR
+
+jsp.error.bad.servlet.engine=Version de moteur de servlet incorrecte!
+jsp.error.no.scratch.dir=Le moteur de JSP engine n''est pas configur\u00e9 avec un r\u00e9pertoire de travail.\
+\n Merci d''ajouter \"jsp.initparams=scratchdir=<dir-name>\" \
+\n dans le fichier "servlets.properties" de ce contexte.
+jsp.error.bad.scratch.dir=Le param\u00eatre "scratchDir" que vous avez sp\u00e9cifi\u00e9: {0} est inutilisable.
+jsp.message.scratch.dir.is=Le r\u00e9pertoire de travail (scratch dir) pour le moteur de JSP est: {0}
+jsp.message.parent_class_loader_is=Le chargeur de classe parent (class loader) est: {0}
+jsp.message.dont.modify.servlets=IMPORTANT: Ne pas modifier les servlets g\u00e9n\u00e9r\u00e9es
+jsp.error.not.impl.comments=Erreur interne: Commentaires non impl\u00e9ment\u00e9s
+jsp.error.not.impl.directives=Erreur interne: Directives non impl\u00e9ment\u00e9es
+jsp.error.not.impl.declarations=Erreur interne: Declarations non impl\u00e9ment\u00e9es
+jsp.error.not.impl.expressions=Erreur interne: Expressions non impl\u00e9ment\u00e9es
+jsp.error.not.impl.scriptlets=Erreur interne: Scriptlets non impl\u00e9ment\u00e9s
+jsp.error.not.impl.usebean=Erreur interne: useBean non impl\u00e9ment\u00e9
+jsp.error.not.impl.getp=Erreur interne: getProperty non impl\u00e9ment\u00e9
+jsp.error.not.impl.setp=Erreur interne: setProperty non impl\u00e9ment\u00e9
+jsp.error.not.impl.plugin=Erreur interne: plugin non impl\u00e9ment\u00e9
+jsp.error.not.impl.forward=Erreur interne: forward non impl\u00e9ment\u00e9
+jsp.error.not.impl.include=Erreur interne: include non impl\u00e9ment\u00e9
+jsp.error.unavailable=La JSP a \u00e9t\u00e9 marqu\u00e9e comme non disponible
+jsp.error.usebean.missing.attribute=useBean: l''identificateur d''attribut (id attribute) est manquant ou mal orthographi\u00e9
+jsp.error.usebean.missing.type=useBean ({0}): La classe ou le type d''attribut doit \u00eatre\
+sp\u00e9cifi\u00e9: 
+jsp.error.usebean.duplicate=useBean: Nom de bean dupliqu\u00e9: {0}
+jsp.error.usebean.prohibited.as.session=Impossible d''utiliser comme bean de session {0} car c''est interdit\
+par la directive jsp d\u00e9finie pr\u00e9c\u00e9demment: 
+jsp.error.usebean.not.both=useBean: Impossible de sp\u00e9cifier \u00e0 la fois la classe et l''attribut beanName: 
+jsp.error.usebean.bad.type.cast=useBean ({0}): Le type ({1}) n''est pas assignable depuis la classe ({2}) 
+jsp.error.classname=Impossible de d\u00e9terminer le nom de classe d''apr\u00e8s le fichier .class
+jsp.warning.bad.type=Attention: mauvais type dans le fichier .class
+jsp.error.data.file.write=Erreur lors de l''\u00e9criture du fichier de donn\u00e9es
+#Directive de Page: valeur incorrecte pour pageEncoding
+jsp.error.page.invalid.contenttype=Directive de Page: valeur incorrecte pour contentType
+jsp.error.page.invalid.session=Directive de Page: valeur incorrecte pour session
+jsp.error.page.invalid.buffer=Directive de Page: valeur incorrecte pour "buffer"
+jsp.error.page.invalid.autoflush=Directive de Page: valeur incorrecte pour autoFlush
+jsp.error.page.invalid.isthreadsafe=Directive de Page: valeur incorrecte pour isThreadSafe
+jsp.error.page.invalid.info=Directive de Page: valeur incorrecte pour info
+jsp.error.page.invalid.iserrorpage=Directive de Page: valeur incorrecte pour isErrorPage
+jsp.error.page.defafteruse.language=Directive de Page: on ne peut d\u00e9finir language apr\u00e8s un scriptlet 
+jsp.error.page.nomapping.language=Directive de Page: Pas de correspondance pour language: 
+jsp.error.page.bad_b_and_a_combo=Directive de Page: combinaison ill\u00e9gale de buffer=\"none\" && autoFlush=\"false\"
+jsp.error.not.impl.taglib=Internal error: Tag extensions non impl\u00e9ment\u00e9s
+jsp.error.include.missing.file=l''argument fichier (file) pour l''inclusion (include) est absent
+jsp.error.include.bad.file=Mauvais argument fichier (file) pour l''inclusion (include)
+jsp.error.include.exception=Impossible d''inclure (include) {0}
+jsp.error.stream.closed=Flux ferm\u00e9
+jsp.error.invalid.forward=Tag forward incorrect
+jsp.error.unknownException=Erreur non trait\u00e9e! Vous devriez penser \u00e0 utiliser une page d''erreur \
+pour rapporter ce type d''erreur plus \u00e9l\u00e9gamment
+jsp.error.invalid.directive=Directive incorrecte
+jsp.error.directive.istagfile=La directive {0} ne peut \u00eatre utilis\u00e9e dans un fichier tag
+jsp.error.directive.isnottagfile=La directive {0} ne peut \u00eatre utilis\u00e9e que dans un fichier tag
+jsp.error.tagfile.tld.name=L''attribut \"name\" de la directive tag contient la valeur {0} alors que le tag \"name\" de l''\u00e9l\u00e9ment \"tag-file\" dans le TLD est {1}
+jsp.error.action.istagfile=L''action {0} ne peut \u00eatre utilis\u00e9e dans un fichier tag
+jsp.error.action.isnottagfile=L''action {0} ne peut \u00eatre utilis\u00e9e que dans un fichier tag
+jsp.error.unterminated=Tag {0} non termin\u00e9
+jsp.error.usebean.notinsamefile=le tag useBean doit commencer et finir dans le m\u00eame fichier physique
+jsp.error.loadclass.taghandler=Impossible de charger la classe {0}
+jsp.error.unable.compile=Impossible de compiler la classe pour la JSP
+jsp.error.unable.load=Impossible de charger la classe pour la JSP
+jsp.error.unable.rename=Impossible de renommer le fichier classe de {0} vers {1}
+jsp.error.mandatory.attribute={0}: L''attribut obligatoire {1} est manquant
+jsp.engine.info=Moteur Jasper JSP 2.2
+jsp.error.invalid.expression="{0}" contient d''incorrecte(s) expression(s): {1}
+jsp.error.invalid.attribute={0}: Attribut incorrect: {1}
+jsp.error.usebean.class.notfound=Classe: {0} non trouv\u00e9e
+jsp.error.file.cannot.read=Impossible de lire le fichier: {0}
+jsp.error.file.already.registered=Inclusion r\u00e9cursive du fichier {0}
+jsp.error.file.not.registered=Le fichier {0} n''appara\u00eet pas dans l''inclusion (include)
+jsp.error.quotes.unterminated=guillemets non termin\u00e9s
+jsp.error.attr.quoted=La valeur de l''attribute doit \u00eatre entre guillemets 
+jsp.error.attr.novalue=L''attribute {0} n''a pas de valeur
+jsp.error.tag.attr.unterminated=Liste de tag d''attribut non termin\u00e9e
+jsp.error.param.noname=Pas de nom dans le tag PARAM
+jsp.error.param.novalue=Pas de valeur dans le tag PARAM
+jsp.error.beans.nullbean=Tentative d''op\u00e9ration bean sur un objet nul.
+jsp.error.beans.nobeaninfo=Pas d''information bean (BeanInfo) pour le bean de type ''{0}'' n''a pu \u00eatre trouv\u00e9e, la classe n''existe probablement pas.
+jsp.error.beans.introspection=Une exception s''est produite lors de l''introspection de la m\u00e9thode read de la propri\u00e9t\u00e9 ''{0}'' dans le bean de type ''{1}'':\n{2}
+jsp.error.beans.nomethod=Impossible de trouver une m\u00e9thode pour lire la propri\u00e9t\u00e9 ''{0}'' dans le bean de type ''{1}''
+jsp.error.beans.nomethod.setproperty=Impossible de trouver une m\u00e9thode pour mettre \u00e0 jour la propri\u00e9t\u00e9 ''{0}'' de type ''{1}''dans le bean de type ''{2}''
+jsp.error.beans.noproperty==Impossible de trouver de l''information sur la propri\u00e9t\u00e9 ''{0}'' dans le bean de type ''{1}''
+jsp.error.beans.setproperty.noindexset=Impossible de renseigner la propri\u00e9t\u00e9 ind\u00e9x\u00e9e
+jsp.error.include.tag=Tag jsp:include incorrect
+jsp.error.include.noflush=jsp:include doit avoir \"flush=true\"
+jsp.error.include.badflush=jsp:include page=\"...\" flush=\"true\" est la seule combinaison valide dans JSP 1.0
+jsp.error.attempt_to_clear_flushed_buffer=Erreur: Tentative d''effacement d''un tampon qui a d\u00e9j\u00e0 \u00e9t\u00e9 vidang\u00e9 (flush)
+jsp.error.overflow=Erreur: D\u00e9passement de capacit\u00e9 du tampon JSP
+jsp.error.paramexpected=Le tag \"param\" est attendu avec les attributs \"name\" et \"value\" apr\u00e8s le tag \"params\".
+jsp.error.closeindividualparam=Le tag param doit \u00eatre ferm\u00e9 avec \"/>\"
+jsp.error.closeparams=Le tag param tag doit \u00eatre ferm\u00e9 avec  /params
+jsp.error.plugin.notype=type non d\u00e9clar\u00e9 dans jsp:plugin
+jsp.error.plugin.nocode=code non d\u00e9clar\u00e9 dans jsp:plugin
+jsp.error.ise_on_clear=Il est interdit d''utiliser clear() quand la taille de tampon== 0
+jsp.error.setproperty.beanNotFound=setProperty: le Bean {0} est introuvable
+jsp.error.getproperty.beanNotFound=getProperty: le Bean {0} est introuvable
+jsp.error.setproperty.ClassNotFound=setProperty: la Classe {0} est introuvable
+jsp.error.setproperty.invalidSyntax=setProperty: On ne peut avoir de valeur non nulle quand property=*
+jsp.error.setproperty.beanInfoNotFound=setproperty: beanInfo pour le bean {0} est introuvable
+jsp.error.setproperty.paramOrValue=setProperty: param ou value doit \u00eatre pr\u00e9sent
+jsp.error.setproperty.arrayVal=setProperty: on ne peut renseigner les array property {0} au travers d''une valeur cha\u00eene constante (string constant value)
+jsp.warning.keepgen=Attention: Valeur incorrecte pour le initParam keepgenerated. Utilisation de la valeur par d\u00e9faut \"false\"
+jsp.warning.enablePooling=Attention: Valeur incorrecte pour le initParam enablePooling. Utilisation de la valeur par d\u00e9faut \"true\"
+jsp.warning.mappedFile=Attention: Valeur incorrecte pour le initParam mappedFile. Utilisation de la valeur par d\u00e9faut \"false\"
+jsp.warning.classDebugInfo=Attention: Valeur incorrecte pour le initParam classdebuginfo. Utilisation de la valeur par d\u00e9faut \"false\"
+jsp.warning.checkInterval=Attention: Valeur incorrecte pour le initParam checkInterval. Utilisation de la valeur par d\u00e9faut \"300\" secondes
+jsp.warning.development=Attention: Valeur incorrecte pour le initParam development. Utilisation de la valeur par d\u00e9faut \"true\"
+jsp.warning.reloading=Attention: Valeur incorrecte pour le initParam reloading. Utilisation de la valeur par d\u00e9faut \"true\"
+jsp.error.badtaglib=Impossible d''ouvrir le taglibrary {0} : {1}
+jsp.error.badGetReader=Impossible de cr\u00e9er un lecteur (reader) quand le flux n''utilse pas des tampons (not buffered)
+jsp.warning.unknown.element.in.tag=Attention: El\u00e9ment inconnu {0} dans le tag
+jsp.warning.unknown.element.in.tagfile=Attention: El\u00e9ment inconnu {0} dans le tag-file
+jsp.warning.unknown.element.in.attribute=Attention: El\u00e9ment inconnu {0} dans l''attribute
+jsp.error.more.than.one.taglib=plus d''un taglib dans le TLD: {0}
+jsp.error.teiclass.instantiation=Impossible de charger ou d''instancier la classe TagExtraInfo: {0}
+jsp.error.non_null_tei_and_var_subelems=Le tag {0} poss\u00e8de une ou plusieurs variables subelements et une classe TagExtraInfo qui retourne une ou plusieurs VariableInfo
+jsp.error.parse.error.in.TLD=Erreur d''\u00e9valuation (parse) dans le descripteur de librairie de tag (TLD): {0}
+jsp.error.unable.to.open.TLD=Impossible d''ouvrir le descripteur de librairie de tag (TLD): {0}
+jsp.buffer.size.zero=Taille du tampon <= 0
+jsp.error.file.not.found=Le fichier \"{0}\" n''a pas \u00e9t\u00e9 trouv\u00e9
+jsp.message.copyinguri=Copie de {0} dans {1}
+jsp.message.htmlcomment=\nEffacement des commentaires: \t{0}
+jsp.message.handling_directive=\nDirective de gestion (handling): {0}\t{1}
+jsp.message.handling_plugin=\nPlugin: {0}
+jsp.message.package_name_is=Le nom de package est: {0}
+jsp.message.class_name_is=Le nom de classe est: {0}
+jsp.message.java_file_name_is=Le nom de fichier Java est: {0}
+jsp.message.class_file_name_is=Le nom de fichier Class est: {0}
+jsp.message.accepted=Accept\u00e9 {0} \u00e0 {1}
+jsp.message.adding_jar=Ajout du jar {0} \u00e0 mon classpath
+jsp.message.compiling_with=Compilation avec: {0}
+jsp.message.template_text=texte template
+jsp.error.missing_attribute=D''apr\u00e8s le TLD l''attribut {0} est obligatoire pour le tag {1}
+jsp.error.bad_attribute=L''attribut {0} est incorrect pour le tag {1} d''apr\u00e8s la TLD indiqu\u00e9e
+jsp.error.webxml_not_found=Impossible de localiser le fichier web.xml
+jsp.cmd_line.usage=Usage: jsptoservlet [-dd <path/to/outputDirectory>] [-keepgenerated] \
+<.jsp files>
+jsp.message.cp_is=Le Classpath {0} est: {1}
+jsp.error.unable.to_load_taghandler_class=Impossible de charger la classe gestionnaire de tag {0} car {1}
+jsp.error.unable.to_find_method=Impossible de trouver une m\u00e9thode de mise \u00e0 jour pour l''attribut: {0}
+jsp.error.unable.to_convert_string=Impossible de convertir une cha\u00eene vers {0} pour l''attribut {1}
+jsp.error.unable.to_introspect=Impossible d''introspecter la classe gestionnaire de tag : {0} car {1}
+jsp.error.bad_tag=Aucun tag {0} dans la librairie de tag import\u00e9e avec le pr\u00e9fixe {1}
+jsp.error.bad_string_Character=Impossible d''extraire un caract\u00e8re depuis un tableau vide
+jsp.error.bad_string_char=Impossible d''extraire un caract\u00e8re depuis un tableau vide
+jsp.warning.compiler.class.cantcreate=Impossible de cr\u00e9er une instance de classe plugin pour le compilateur indiqu\u00e9 {0} due to {1}. Utilisation par d\u00e9faut du Compilateur Java Sun.
+jsp.warning.compiler.class.notfound=La classe plugin de compilateur {0} est introuvable. Utilisation par d\u00e9faut du Compilateur Java Sun.
+jsp.warning.compiler.path.notfound=le chemin de compilateur indiqu\u00e9 {0} est introuvable. Utilisation par d\u00e9faut du chemin syst\u00e8me (system PATH).
+jsp.error.jspc.uriroot_not_dir=L''option -uriroot doit indiqu\u00e9 un r\u00e9pertoire d\u00e9j\u00e0 existant
+jspc.implicit.uriRoot=uriRoot r\u00e9gl\u00e9 implicitement \u00e0 "{0}"
+jspc.usage=Usage: jspc <options> [--] <fichiers jsp>\n\
+o\u00f9 les fichiers jsp sont n''importe quel nombre de:\n\
+\    <file>         Un fichier \u00e0 \u00e9valuer (parser) comme page jsp\n\
+\    -webapp <dir>  Un r\u00e9pertoire contenant une application web, toutes les pages jsp\n\
+\                   seront r\u00e9cursivement \u00e9valu\u00e9es\n\
+o\u00f9 les options comprennet:\n\
+\    -q          Mode silencieux (identique \u00e0 -v0)\n\
+\    -v[#]       Mode bavard (Le nombre optionnel indique le niveau, 2 par d\u00e9faut)\n\
+\    -d <dir>    Dossier de sortie\n\
+\    -dd <dir>   Dossier de sortie literal.  (Les dossiers de paquets ne seront pas construits)\n\
+\    -l          Sortie du nom la page JSP en cas d''\u00e9chec\n\
+\    -s          Sortie du nom la page JSP en cas de succ\u00e8s\n\
+\    -p <name>   Nom du paquet cible\n\
+\    -c <name>   Nom d'un nom de classe cible\n\
+\                (s''applique seulement \u00e0 la premi\u00e8re page JSP)\n\
+\    -mapped     G\u00e9n\u00e8re des appels \u00e0 write() s\u00e9par\u00e9s pour chaque ligne HTML dans la JSP\n\
+\    -die[#]     G\u00e9n\u00e8re un code d''erreur de retour (#) en cas d''erreurs fatales.\n\
+\                Si le nombre est absent ou non num\u00e9rique, le d\u00e9faut est 1.\n\
+\    -uribase <dir>  Le r\u00e9pertoire uri de compilations relatif\n\
+\                    (Par d\u00e9faut "/")\n\
+\    -uriroot <dir>  The r\u00e9pertoire racine contre lequel les fichiers seront r\u00e9solus\n\
+\                    , (Par d\u00e9faut le r\u00e9pertoire depuis lequel jspc est appel\u00e9)\n\
+\    -webinc <file>  Cr\u00e9ation d''association partielle de servlet pour l''option -webapp.\n\
+\    -webxml <file>  Cr\u00e9ation d''un fichier web.xml complet pour l''option -webapp.\n\
+\    -ieplugin <clsid>  Le classid du Plugin Java Plugin pour Internet Explorer\n\
+\    -sax2 <driverclassname>  Le nom de classe du Driver SAX 2.0 \u00e0 utiliser\n\
+\    -trimSpaces        Trim spaces in template text between actions, directives\n\
+\    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.6)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.6)\n\
+
+jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
+\n\
+<!DOCTYPE web-app\n\
+\    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+\    "http://java.sun.com/dtd/web-app_2_3.dtd">\n\
+<!--\n\
+Cr\u00e9er automatiquement par le JspC Apache Tomcat.\n\
+-->\n\
+<web-app>\n\
+\n
+jspc.webxml.footer=\n\
+</web-app>\n\
+\n
+jspc.webinc.header=\n\
+<!--\n\
+Cr\u00e9er automatiquement par le JspC Apache Tomcat.\n\
+Placez ce fragment dans le fichier web.xml avant all icon, display-name,\n\
+description, distributable, and context-param elements.\n\
+-->\n
+jspc.webinc.footer=\n\
+<!--\n\
+All session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+resource-ref, security-constraint, login-config, security-role,\n\
+env-entry, and ejb-ref elements should follow this fragment.\n\
+-->\n
+jspc.error.jasperException=erreur-le fichier ''{0}'' a g\u00e9n\u00e9r\u00e9 l''exception d''\u00e9valuation suivante: {1}
+jspc.error.generalException=ERREUR-le fichier ''{0}'' a g\u00e9n\u00e9r\u00e9 l''exception g\u00e9n\u00e9rale suivante:
+jspc.error.fileDoesNotExist=L''argument fichier ''{0}'' n''existe pas
+jspc.error.emptyWebApp=-webapp n\u00e9cessite \u00e0 sa suite un argument fichier
+jsp.error.library.invalid=La page JSP page est incorrecte d''apr\u00e8s la librairie {0}: {1}
+jsp.error.tlvclass.instantiation=Impossible de charger ou d''instancier la classe TagLibraryValidator: {0}
+jsp.error.tlv.invalid.page=Message d''erreurs de validation provenant du TagLibraryValidator pour {0} en {1}
+jsp.error.tei.invalid.attributes=Message d''erreurs de validation provenant du TagExtraInfo pour {0}
+jsp.parser.sax.propertynotsupported=Propri\u00e9t\u00e9 SAX non support\u00e9e: {0}
+jsp.parser.sax.propertynotrecognized=Propri\u00e9t\u00e9 SAX non reconnue: {0}
+jsp.parser.sax.featurenotsupported=Fonctionnalit\u00e9 SAX non support\u00e9e: {0}
+jsp.parser.sax.featurenotrecognized=Fonctionnalit\u00e9 SAX non reconnue: {0}
+jsp.error.no.more.content=Fin de contenu alors que l''\u00e9valution n''\u00e9tait pas termin\u00e9e: erreur de tags imbriqu\u00e9s?
+jsp.error.parse.xml=Erreur d''\u00e9valuation XML sur le fichier {0}
+jsp.error.parse.xml.line=Erreur d''\u00e9valuation XML sur le fichier  {0}: (ligne {1}, col {2})
+jsp.error.parse.xml.scripting.invalid.body=Le corps de l''\u00e9l\u00e9ment {0} ne doit contenir aucun \u00e9l\u00e9ments XML
+jsp.error.internal.tldinit=Exception lors de l'initialisation de TldLocationsCache: {0}
+jsp.error.internal.filenotfound=Erreur interne: Fichier {0} introuvable
+jsp.error.internal.evaluator_not_found=Erreur interne: Impossible de charger l''\u00e9valuateur d''expression
+jsp.error.parse.xml.invalidPublicId=PUBLIC ID invalide: {0}
+jsp.error.include.flush.invalid.value=Valeur incorrecte pour l''attribut flush: {0}
+jsp.error.unsupported.encoding=Encodage non support\u00e9: {0}
+jsp.warning.unknown.element.in.variable=Attention: Element inconnu {0} dans la variable
+tld.error.variableNotAllowed=Ceci est une erreur pour le tag qui poss\u00e8de une ou plusieurs variables subelements pour avoir une classe TagExtraInfo qui retourne un objet non-nul.
+jsp.error.tldInWebDotXmlNotFound=Ne peut trouver le TLD {1} pour l''URI {0} indiqu\u00e9e dans le fichier web.xml
+jsp.error.taglibDirective.absUriCannotBeResolved=L''uri absolue: {0} ne peut \u00eatre r\u00e9solu dans le fichier web.xml ou dans les fichiers jar d\u00e9ploy\u00e9s avec cette application
+jsp.error.taglibDirective.missing.location=Ni l''uri' ni l''attribut 'tagdir' n''ont \u00e9t\u00e9 indiqu\u00e9s dans la directive taglib
+jsp.error.invalid.tagdir=Le r\u00e9pertoire du fichier Tag {0} ne commence pas par \"/WEB-INF/tags\"
+jsp.error.unterminated.user.tag=Tag user-defined non termin\u00e9: Le tag de fermeture {0} est introuvable found ou incorrectement imbriqu\u00e9
+#jspx.error.templateDataNotInJspCdata=Erreur de validation: l''\u00e9l\u00e9ment &lt;{0}&gt; ne peut avoir de donn\u00e9es template. Les donn\u00e9es Template doivent \u00eatre encapsul\u00e9es \u00e0 l''int\u00e9rieur d''un \u00e9l\u00e9ment &lt;jsp:cdata&gt;. [JSP1.2 PFD section 5.1.9]\nDonn\u00e9e Template en erreur: {1}
+jspx.error.templateDataNotInJspCdata=Erreur de validation: l''\u00e9l\u00e9ment &lt;{0}&gt; ne peut avoir de donn\u00e9es template. Les donn\u00e9es Template doivent \u00eatre encapsul\u00e9es \u00e0 l''int\u00e9rieur d''un \u00e9l\u00e9ment &lt;jsp:text&gt;. [JSP1.2 PFD section 5.1.9]\nDonn\u00e9e Template en erreur: {1}
+#Erreur lors du traitement du fichier jar de la taglib {0}: {1}
+jsp.error.taglib.reserved.prefix=Le pr\u00e9fixe taglib {0} est r\u00e9serv\u00e9
+jsp.error.invalid.javaEncoding=Encodage java incorrect. Essai de {0} puis de {1}. Les deux ont \u00e9chou\u00e9.
+jsp.error.needAlternateJavaEncoding=L''encodage java par d\u00e9faut {0} est incorrect sur votre environnement java. Une alternative peut \u00eatre indiqu\u00e9e via le param\u00eatre 'javaEncoding' de la JspServlet.
+#Erreur lors de la compilation, utilis\u00e9 pour la ligne jsp des messages d''erreur
+jsp.error.single.line.number=Une erreur s''est produite \u00e0 la ligne: {0} dans le fichier jsp: {1}
+jsp.error.multiple.line.number=\n\nUne erreur s''est produite entre les lignes: {0} et {1} dans le fichier jsp: {2}\n\n
+jsp.error.corresponding.servlet=Erreur de servlet g\u00e9n\u00e9r\u00e9e:\n
+jsp.error.empty.body.not.allowed=Un corps vide n'est pas autoris\u00e9 pour {0}
+jsp.error.jspbody.required=Doit utiliser jsp:body pour indiqu\u00e9 le corps de tag body de {0} si jsp:attribute est utilis\u00e9.
+jsp.error.jspbody.emptybody.only=Le tag {0} ne peut avoir que jsp:attribute dans son corps.
+jsp.error.no.scriptlets=Les \u00e9l\u00e9ments de Scripting ( <%!, <jsp:declaration, <%=, <jsp:expression, <%, <jsp:scriptlet ) ne sont pas autoris\u00e9s ici.
+jsp.error.internal.unexpected_node_type=Erreur Interne: Type de node inattendu rencontr\u00e9
+jsp.error.tld.fn.invalid.signature=Synthaxe invalide pour la signature de fonction dans la TLD.  Librairie de Tag : {0}, Fonction: {1}
+jsp.error.tld.fn.invalid.signature.commaexpected=Synthaxe invalide pour la signature de fonction dans la TLD.  Virgule ',' attendue.  Librairie de Tag: {0}, Fonction: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected=Synthaxe invalide pour la signature de fonction dans la TLD.  Parenth\u00e8se '(' attendue.  Librairie de Tag: {0}, Fonction: {1}.
+jsp.error.dynamic.attributes.not.implemented=Le tag {0} indique qu''il accepte des attributs dynamics mais n''impl\u00e9mente pas l''interface requise
+jsp.error.nomatching.fragment=Ne peut trouver une directive attribut (avec pour nom={0} et fragment=true) avant la directive fragment.
+jsp.error.attribute.noequal=Symbole \u00e9gal (equal) attendu
+jsp.error.attribute.noquote=Symbole guillemet (quote) attendu
+jsp.error.attribute.unterminated=L''attribut pour {0} n''est pas correctement termin\u00e9
+jsp.error.missing.tagInfo=L''objet TagInfo de {0} est absent de la TLD
+jsp.error.fragmentwithtype=On ne peut indiquer \u00e0 la fois les attributs 'fragment' et 'type'.  Si 'fragment' est pr\u00e9sent, 'type' est fix\u00e9 comme 'javax.servlet.jsp.tagext.JspFragment'
+jsp.error.fragmentwithrtexprvalue=On ne peut indiquer \u00e0 la fois les attributs 'fragment' et 'rtexprvalue'.  Si 'fragment' est pr\u00e9sent, 'rtexprvalue' est fix\u00e9 \u00e0 'true'
+jsp.error.fragmentWithDeclareOrScope=Les attributs 'fragment' et 'declare' ou 'scope' sont indiqu\u00e9s dans la directive variable
+jsp.error.var_and_varReader=A la fois 'var' et 'varReader' sont indiqu\u00e9s
+jsp.warning.bad.urlpattern.propertygroup=Mauvaise valeur {0} dans le sous-\u00e9l\u00e9ment (subelement) url-pattern du fichier web.xml
+jsp.error.unknown_attribute_type=Type d''attribut inconnu ({1}) pour l''attribut {0}.
+jsp.error.jspelement.missing.name=L''attribut obligatoire 'name' est absent de jsp:element
+jsp.error.xmlns.redefinition.notimplemented=Erreur Interne: Tentative de red\u00e9finition de xmlns:{0}.  La red\u00e9finition des domaines de noms (namespaces) n''est pas impl\u00e9ment\u00e9e.
+jsp.error.could.not.add.taglibraries=Impossible d''ajouter une ou plusieurs librairies de tag.
+jsp.error.duplicate.name.jspattribute=L''attribut {0} indiqu\u00e9 dans l''action standard ou sp\u00e9cifique (custom) apparait aussi comme valeur de l''attribut de nom dans le jsp:attribute inclus
+jsp.error.not.in.template={0} n''est pas autoris\u00e9 dans le corps de texte de template.
+jsp.error.badStandardAction=L''action n''est pas reconnue comme une action standard.
+jsp.error.tagdirective.badbodycontent=Contenu de corps (body-content) ({0}) invalide dans la directive tag
+jsp.error.config_pagedir_encoding_mismatch=L''encode de page (Page-encoding) indiqu\u00e9 dans le jsp-property-group ({0}) est diff\u00e9rent de celui indiqu\u00e9 dans la directive de page ({1})
+#jsp.error.prolog_pagedir_encoding_mismatch=
+#jsp.error.prolog_config_encoding_mismatch=
+jsp.error.attribute.custom.non_rt_with_expr=D''apr\u00e8s la TLD, l''attribut {0} n''accepte aucune expression
+jsp.error.scripting.variable.missing_name=Incapable de d\u00e9terminer le nom de variable scripting d''apr\u00e8s l''attribut {0}
+jasper.error.emptybodycontent.nonempty=D''apr\u00e8s la TLD, le tag {0} doit \u00eatre vide, mais ne l''est pas
+#jsp.error.tagfile.nameNotUnique=
+#jsp.error.tagfile.nameFrom.noAttribute=
+#jsp.error.tagfile.nameFrom.badAttribute=
+jsp.error.usebean.noSession=Il est ill\u00e9gal pour useBean d''utiliser une port\u00e9e de session (session scope) quand la page JSP indique (via la directive de page) qu''elle ne participe pas aux sessions
+jsp.error.attributes.not.allowed = {0} ne doit avoir aucun attribut
+#jsp.error.nested.jspattribute=
+#jsp.error.nested.jspbody=
+#jsp.error.variable.either.name=
+#jsp.error.variable.both.name=
+#jsp.error.variable.alias=
+#jsp.error.jsptext.badcontent=
+#jsp.error.prefix.refined=
+#jsp.error.jspoutput.conflict=
+#jsp.error.jspoutput.doctypenamesystem=
+#jsp.error.jspoutput.doctypepulicsystem=
+#jsp.error.jspoutput.nonemptybody=
+#jsp.error.jspoutput.invalidUse=
+#jsp.error.invalid.bean=
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_ja.properties
new file mode 100644
index 0000000..c997e71
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/resources/LocalStrings_ja.properties
@@ -0,0 +1,417 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# $Id: LocalStrings_ja.properties,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+#
+# Default localized string information
+# Localized this the Default Locale as is ja_JP
+
+jsp.error.bad.servlet.engine=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30a8\u30f3\u30b8\u30f3\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093
+jsp.error.no.scratch.dir=JSP\u30a8\u30f3\u30b8\u30f3\u306b\u30c7\u30d5\u30a9\u30eb\u30c8\u306escratchDir\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\
+\n \u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306eservlets.properties\u30d5\u30a1\u30a4\u30eb\u306b\u3001\
+\n \"jsp.initparams=scratchdir=<dir-name>\" \u3092\u8ffd\u52a0\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+jsp.error.bad.scratch.dir=\u3042\u306a\u305f\u304c\u6307\u5b9a\u3057\u305fscratchDir: {0} \u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.message.scratch.dir.is=JSP\u30a8\u30f3\u30b8\u30f3\u306eScratchdir: {0}
+jsp.message.parent_class_loader_is=\u89aa\u30af\u30e9\u30b9\u30ed\u30fc\u30c0: {0}
+jsp.message.dont.modify.servlets=\u91cd\u8981: \u751f\u6210\u3055\u308c\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u3092\u5909\u66f4\u3057\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.not.impl.comments=\u5185\u90e8\u30a8\u30e9\u30fc: Comments\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.directives=\u5185\u90e8\u30a8\u30e9\u30fc: Directives\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.declarations=\u5185\u90e8\u30a8\u30e9\u30fc: Declarations\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.expressions=\u5185\u90e8\u30a8\u30e9\u30fc: Expressions\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.scriptlets=\u5185\u90e8\u30a8\u30e9\u30fc: Scriptlets\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.usebean=\u5185\u90e8\u30a8\u30e9\u30fc: useBean\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.getp=\u5185\u90e8\u30a8\u30e9\u30fc: getProperty\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.setp=\u5185\u90e8\u30a8\u30e9\u30fc: setProperty\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.plugin=\u5185\u90e8\u30a8\u30e9\u30fc: plugin\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.forward=\u5185\u90e8\u30a8\u30e9\u30fc: forward\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.not.impl.include=\u5185\u90e8\u30a8\u30e9\u30fc: include\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.unavailable=JSP\u306f\u5229\u7528\u4e0d\u53ef\u3068\u30de\u30fc\u30af\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.usebean.missing.attribute=useBean: id\u5c5e\u6027\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u30b9\u30da\u30eb\u30df\u30b9\u3067\u3059
+jsp.error.usebean.missing.type=useBean ({0}): class\u5c5e\u6027\u304btype\u5c5e\u6027\u3092\u6307\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044:
+jsp.error.usebean.duplicate=useBean: beanName\u5c5e\u6027\u304c\u91cd\u8907\u3057\u3066\u3044\u307e\u3059: {0}
+jsp.error.usebean.prohibited.as.session=\u4ee5\u524d\u306b\u5b9a\u7fa9\u3057\u305fJSP\u6307\u793a\u5b50\u306b\u3088\u3063\u3066\u7981\u6b62\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u306b\u3001session bean {0} \u3068\u3057\u3066\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093:
+jsp.error.usebean.not.both=useBean: class\u5c5e\u6027\u3068beanName\u5c5e\u6027\u306e\u4e21\u65b9\u3092\u540c\u6642\u306b\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093:
+jsp.error.usebean.bad.type.cast=useBean ({0}): type ({1}) \u306fclass ({2}) \u304b\u3089\u5272\u308a\u5f53\u3066\u3089\u308c\u307e\u305b\u3093
+jsp.error.invalid.scope='scope'\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059: {0} (\"page\"\u3001\"request\"\u3001\"session\"\u53c8\u306f\"application\"\u306e\u3069\u308c\u304b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093)
+jsp.error.classname=.class\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u30af\u30e9\u30b9\u540d\u3092\u6c7a\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.warning.bad.type=\u8b66\u544a: .class\u30d5\u30a1\u30a4\u30eb\u4e2d\u306e\u578b\u304c\u9055\u3044\u307e\u3059
+jsp.error.data.file.write=\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u3092\u66f8\u304d\u8fbc\u307f\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+jsp.error.page.conflict.contenttype=page\u6307\u793a\u5b50: 'contentType'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.contenttype=page\u6307\u793a\u5b50: contentType\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.session=page\u6307\u793a\u5b50: 'session'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.session=page\u6307\u793a\u5b50: session\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.buffer=page\u6307\u793a\u5b50: 'buffer'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.buffer=page\u6307\u793a\u5b50: buffer\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.autoflush=page\u6307\u793a\u5b50: 'autoFlush'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.autoflush=page\u6307\u793a\u5b50: autoFlush\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.isthreadsafe=page\u6307\u793a\u5b50: 'isThreadSafe'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.isthreadsafe=page\u6307\u793a\u5b50: isThreadSafe\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.info=page\u6307\u793a\u5b50: 'info'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.info=page\u6307\u793a\u5b50: info\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.iserrorpage=page\u6307\u793a\u5b50: 'isErrorPage'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.iserrorpage=page\u6307\u793a\u5b50: isErrorPage\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.page.conflict.errorpage=page\u6307\u793a\u5b50: 'errorPage'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.language=page\u6307\u793a\u5b50: 'language'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.tag.conflict.language=tag\u6307\u793a\u5b50: 'language'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.language.nonjava=page\u6307\u793a\u5b50: \u7121\u52b9\u306alanguage\u5c5e\u6027\u3067\u3059
+jsp.error.tag.language.nonjava=tag\u6307\u793a\u5b50: \u7121\u52b9\u306alanguage\u5c5e\u6027\u3067\u3059
+jsp.error.page.defafteruse.language=page\u6307\u793a\u5b50: scriptlet\u306e\u5f8c\u3067language\u5c5e\u6027\u3092\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.error.page.nomapping.language=page\u6307\u793a\u5b50 language\u5c5e\u6027\u306e\u30de\u30c3\u30d4\u30f3\u30b0\u304c\u5b58\u5728\u3057\u307e\u305b\u3093:
+jsp.error.page.conflict.extends=page\u6307\u793a\u5b50: 'extends'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.iselignored=page\u6307\u793a\u5b50: 'isELIgnored'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.tag.conflict.iselignored=tag\u6307\u793a\u5b50: 'isELIgnored'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.invalid.iselignored=page\u6307\u793a\u5b50: isELIgnored\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.tag.invalid.iselignored=tag\u6307\u793a\u5b50: isELIgnored\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.page.multi.pageencoding=page\u6307\u793a\u5b50\u306f\u8907\u6570\u306epageencoding\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.tag.conflict.attr=Tag\u6307\u793a\u5b50: \u5c5e\u6027\"{0}\"\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u4e0d\u6b63\u3067\u3059 (\u65e7: {1}, \u65b0: {2})
+jsp.error.tag.multi.pageencoding=tag\u6307\u793a\u5b50\u306f\u8907\u6570\u306epageencoding\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.page.bad_b_and_a_combo=page\u6307\u793a\u5b50: buffer=\"none\"\u3068autoFlush=\"false\"\u3092\u540c\u6642\u306b\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.not.impl.taglib=\u5185\u90e8\u30a8\u30e9\u30fc: \u30bf\u30b0\u62e1\u5f35\u5b50\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.include.missing.file=\u53d6\u308a\u8fbc\u3080\u30d5\u30a1\u30a4\u30eb\u5f15\u6570\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.include.bad.file=include\u5c5e\u6027\u306e\u30d5\u30a1\u30a4\u30eb\u5f15\u6570\u304c\u9593\u9055\u3063\u3066\u3044\u307e\u3059
+jsp.error.include.exception={0} \u3092\u53d6\u308a\u8fbc\u3081\u307e\u305b\u3093
+jsp.error.stream.closed=\u30b9\u30c8\u30ea\u30fc\u30e0\u304c\u30af\u30ed\u30fc\u30ba\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.invalid.forward=\u7121\u52b9\u306aforward\u30bf\u30b0\u3067\u3059
+jsp.error.unknownException=\u51e6\u7406\u4e0d\u53ef\u80fd\u306a\u30a8\u30e9\u30fc\u3067\u3059! \u3053\u306e\u3088\u3046\u306a\u30a8\u30e9\u30fc\u3092\u3088\u308a\u8a73\u7d30\u306b\u5831\u544a\u3059\u308b\u30a8\u30e9\u30fc\u30da\u30fc\u30b8\u3092\u6301\u3063\u305f\u65b9\u304c\u3088\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093
+jsp.error.invalid.directive=\u7121\u52b9\u306a\u6307\u793a\u5b50\u3067\u3059
+jsp.error.directive.istagfile={0} \u6307\u793a\u5b50\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.directive.isnottagfile={0} \u6307\u793a\u5b50\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u3057\u304b\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.tagfile.tld.name=TLD\u4e2d\u306e\u30bf\u30b0\u6307\u793a\u5b50\u306e\"tag-file\"\u8981\u7d20\u306e\"name\"\u30bf\u30b0\u306f {1} \u3067\u3059\u304c\uff0c\"name\"\u5c5e\u6027\u306f\u5024 {0} \u3092\u6301\u3063\u3066\u3044\u307e\u3059
+jsp.error.action.istagfile={0} \u30a2\u30af\u30b7\u30e7\u30f3\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.action.isnottagfile={0} \u30a2\u30af\u30b7\u30e7\u30f3\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u306e\u307f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+jsp.error.unterminated={0} \u30bf\u30b0\u304c\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.usebean.notinsamefile=useBean\u30bf\u30b0\u306f\u3001\u540c\u4e00\u30d5\u30a1\u30a4\u30eb\u4e2d\u3067\u958b\u59cb\u3057\u3001\u7d42\u4e86\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.loadclass.taghandler=\u30bf\u30b0 \"{1}\" \u306b\u30bf\u30b0\u30cf\u30f3\u30c9\u30e9\u30af\u30e9\u30b9 \"{0}\" \u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.compile=JSP\u306e\u30af\u30e9\u30b9\u3092\u30b3\u30f3\u30d1\u30a4\u30eb\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.load=JSP\u306e\u30af\u30e9\u30b9\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.rename=\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb {0} \u3092 {1} \u306b\u30d5\u30a1\u30a4\u30eb\u540d\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093
+jsp.error.mandatory.attribute={0}: \u5fc5\u9808\u5c5e\u6027 {1} \u304c\u3042\u308a\u307e\u305b\u3093
+jsp.engine.info=Jasper JSP 2.2\u30a8\u30f3\u30b8\u30f3
+jsp.error.invalid.expression="{0}" \u306f\u7121\u52b9\u306a\u5f0f\u3092\u542b\u3093\u3067\u3044\u307e\u3059: {1}
+jsp.error.invalid.attribute={0}\u306f\u7121\u52b9\u306a\u5c5e\u6027\u3092\u6301\u3063\u3066\u3044\u307e\u3059: {1}
+jsp.error.usebean.class.notfound=\u30af\u30e9\u30b9: {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.file.cannot.read=\u30d5\u30a1\u30a4\u30eb\u304c\u8aad\u3081\u307e\u305b\u3093: {0}
+jsp.error.file.already.registered=\u30d5\u30a1\u30a4\u30eb {0} \u306e\u518d\u5e30\u7684\u306a\u53d6\u308a\u8fbc\u307f\u3067\u3059
+jsp.error.file.not.registered=include\u5c5e\u6027\u4e2d\u306e\u30d5\u30a1\u30a4\u30eb {0} \u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+jsp.error.quotes.unterminated=\u5f15\u7528\u7b26\u304c\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.attr.quoted=\u5c5e\u6027\u5024\u306f\u5f15\u7528\u7b26\u3067\u56f2\u308f\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.attr.novalue=\u5c5e\u6027 {0} \u306b\u306f\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.tag.attr.unterminated=\u30bf\u30b0\u306e\u5c5e\u6027\u30ea\u30b9\u30c8\u304c\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.param.noname=PARAM\u30bf\u30b0\u306bname\u5c5e\u6027\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.param.novalue=PARAM\u30bf\u30b0\u306bvalue\u5c5e\u6027\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.beans.nullbean=null\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306bBean\u64cd\u4f5c\u3092\u304a\u3053\u306a\u304a\u3046\u3068\u3057\u307e\u3057\u305f
+jsp.error.beans.nobeaninfo=\u30bf\u30a4\u30d7 ''{0}'' \u306eBean\u306bBeanInfo\u304c\u306a\u3044\u306e\u3092\u691c\u51fa\u3057\u307e\u3057\u305f, \u30af\u30e9\u30b9\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093
+jsp.error.beans.introspection=\u30bf\u30a4\u30d7 ''{1}'' \u306eBean\u4e2d\u306e\u5c5e\u6027 ''{0}'' \u306eread\u30e1\u30bd\u30c3\u30c9\u3092\u5185\u7701\u4e2d\u306b\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f:\n{2}
+jsp.error.beans.nomethod=\u30bf\u30a4\u30d7 ''{1}'' \u306eBean\u4e2d\u306e\u5c5e\u6027 ''{0}'' \u3092\u8aad\u307f\u8fbc\u3080\u30e1\u30bd\u30c3\u30c9\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+jsp.error.beans.nomethod.setproperty=\u30bf\u30a4\u30d7''{2}''\u306eBean\u306e\u30bf\u30a4\u30d7 ''{1}'' \u306e\u5c5e\u6027 ''{0}'' \u3092\u66f8\u304d\u8fbc\u3080\u30e1\u30bd\u30c3\u30c9\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+jsp.error.beans.noproperty=\u30bf\u30a4\u30d7 ''{1}'' \u306ebean\u4e2d\u306e\u5c5e\u6027 ''{0}'' \u306e\u60c5\u5831\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+jsp.error.beans.setproperty.noindexset=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4ed8\u304d\u306e\u5c5e\u6027\u3092\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.error.include.tag=\u7121\u52b9\u306ajsp:include\u30bf\u30b0\u3067\u3059
+jsp.error.include.noflush=jsp:include\u30bf\u30b0\u306b \"flush=true\" \u3092\u5b9a\u7fa9\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.include.badflush=jsp:include page=\"...\" flush=\"true\" \u306f\u3001JSP 1.0\u3067\u306e\u307f\u6709\u52b9\u306a\u7d44\u307f\u5408\u308f\u305b\u3067\u3059
+jsp.error.attempt_to_clear_flushed_buffer=\u30a8\u30e9\u30fc: \u65e2\u306b\u30d5\u30e9\u30c3\u30b7\u30e5\u3055\u308c\u3066\u3044\u308b\u30d0\u30c3\u30d5\u30a1\u3092\u30af\u30ea\u30a2\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f
+jsp.error.overflow=\u30a8\u30e9\u30fc: JSP\u30d0\u30c3\u30d5\u30a1\u304c\u30aa\u30fc\u30d0\u30fc\u30d5\u30ed\u30fc\u3057\u307e\u3057\u305f
+jsp.error.paramexpected=\"name\"\u5c5e\u6027 \u3068 \"value\" \u5c5e\u6027\u3092\u6301\u3064 \"jsp:param\" \u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.param.invalidUse=jsp:include\u3001jsp:forward\u3001\u53c8\u306fjsp:params\u8981\u7d20\u306e\u5916\u3067jsp:param\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u4f7f\u7528\u3057\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.params.invalidUse=jsp:params\u306fjsp:plugin\u306e\u76f4\u63a5\u306e\u5b50\u4f9b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.fallback.invalidUse=jsp:fallback\u306fjsp:plugin\u306e\u76f4\u63a5\u306e\u5b50\u4f9b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.namedAttribute.invalidUse=jsp:attribute\u306f\u6a19\u6e96\u53c8\u306f\u30ab\u30b9\u30bf\u30e0\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u526f\u8981\u7d20\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspbody.invalidUse=jsp:body\u306f\u6a19\u6e96\u53c8\u306f\u30ab\u30b9\u30bf\u30e0\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u526f\u8981\u7d20\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.closeindividualparam=param\u30bf\u30b0\u306f \"/>\" \u3067\u9589\u3058\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.closeparams=param\u30bf\u30b0\u306f/params\u3067\u9589\u3058\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.params.emptyBody=jsp:params\u306f\u5c11\u306a\u304f\u3068\u3082\u4e00\u3064\u306e\u30cd\u30b9\u30c8\u3057\u305fjsp:param\u3092\u542b\u307e\u306d\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.params.illegalChild=jsp:params\u306fjsp:param\u4ee5\u5916\u306e\u30cd\u30b9\u30c8\u3057\u305f\u8981\u7d20\u3092\u542b\u3093\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.plugin.notype=jsp:plugin\u3067type\u5c5e\u6027\u304c\u5ba3\u8a00\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.plugin.badtype=jsp:plugin\u306e 'type'\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059: 'bean'\u53c8\u306f'applet'\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.plugin.nocode=jsp:plugin\u3067code\u5c5e\u6027\u304c\u5ba3\u8a00\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.ise_on_clear=\u30d0\u30c3\u30d5\u30a1\u30b5\u30a4\u30ba\u304c0\u306e\u6642\u306bclear()\u3092\u5b9f\u884c\u3057\u3066\u3082\u7121\u52b9\u3067\u3059
+jsp.error.setproperty.beanNotFound=setProperty: Bean {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.getproperty.beanNotFound=getProperty: Bean {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.setproperty.ClassNotFound=setProperty: \u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.setproperty.invalidSyntax=setProperty: property=*\u306e\u5834\u5408\u306fnull\u3067\u306a\u3044\u5024\u3092\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.error.setproperty.beanInfoNotFound=setproperty: Bean {0} \u306ebeanInfo\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.setproperty.paramOrValue=setProperty: param\u5c5e\u6027\u304bvalue\u5c5e\u6027\u306e\u3069\u3061\u3089\u304b\u4e00\u3064\u3060\u3051\u3092\u6307\u5b9a\u3067\u304d\u307e\u3059
+jsp.error.setproperty.arrayVal=setProperty: \u5c5e\u6027\u914d\u5217 {0} \u3092\u6587\u5b57\u5217\u5b9a\u6570\u5024\u3067\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093
+jsp.warning.keepgen=\u8b66\u544a: initParam keepgenerated\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002 \u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.xpoweredBy=\u8b66\u544a: Invalid value for the initParam xpoweredBy\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.enablePooling=\u8b66\u544a: initParam enablePooling\u304c\u7121\u52b9\u306a\u5024\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.invalidTagPoolSize=\u8b66\u544a: tagPoolSize\u306e\u521d\u671f\u30d1\u30e9\u30e1\u30fc\u30bf\u304c\u7121\u52b9\u306a\u5024\u3067\u3059\u3002{0}\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u30b5\u30a4\u30ba\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.mappedFile=\u8b66\u544a: initParam mappedFile\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.classDebugInfo=\u8b66\u544a: initParam classDebugInfo\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\"\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.checkInterval=\u8b66\u544a: initParam checkInterval\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"300\"\u79d2\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.development=\u8b66\u544a: initParam development\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.fork=\u8b66\u544a: initParam fork\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.reloading=\u8b66\u544a: initParam reloading\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"true\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.dumpSmap=\u8b66\u544a: initParam dumpSmap\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"false\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.genchararray=\u8b66\u544a: initParam genStrAsCharArray\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\"false\"\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.warning.suppressSmap=\u8b66\u544a: initParam suppressSmap\u306e\u5024\u304c\u7121\u52b9\u3067\u3059\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u5024 \"false\" \u3092\u4f7f\u7528\u3057\u307e\u3059
+jsp.error.badtaglib=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea {0} \u3092\u30aa\u30fc\u30d7\u30f3\u3067\u304d\u307e\u305b\u3093: {1}
+jsp.error.badGetReader=\u30b9\u30c8\u30ea\u30fc\u30e0\u304c\u30d0\u30c3\u30d5\u30a1\u30ea\u30f3\u30b0\u3055\u308c\u3066\u3044\u306a\u3044\u5834\u5408\u306b\u306f\u3001Reader\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093
+jsp.warning.unknown.element.in.taglib=taglib\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.tag=tag\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.tagfile=tag-file\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.attribute=attribute\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.variable=variable\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.validator=validator\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.initParam=validator\u306einit-param\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.warning.unknown.element.in.function=function\u4e2d\u306b\u672a\u77e5\u306e\u8981\u7d20 ({0}) \u304c\u3042\u308a\u307e\u3059
+jsp.error.more.than.one.taglib=TLD\u306e\u4e2d\u306b\u8907\u6570\u306etaglib\u304c\u5b58\u5728\u3057\u307e\u3059: {0}
+jsp.error.teiclass.instantiation=TagExtraInfo class\u306e\u30ed\u30fc\u30c9\u53c8\u306f\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u5316\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+jsp.error.non_null_tei_and_var_subelems=\u30bf\u30b0 {0} \u306f\u4e00\u3064\u4ee5\u4e0a\u306evariable\u526f\u8981\u7d20\u3068\u4e00\u3064\u4ee5\u4e0a\u306eVariableInfo\u3092\u8fd4\u3059TagExtraInfo\u30af\u30e9\u30b9\u3092\u6301\u3063\u3066\u3044\u307e\u3059
+jsp.error.parse.error.in.TLD=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u8a18\u8ff0\u5b50 {0} \u4e2d\u306e\u89e3\u6790\u30a8\u30e9\u30fc\u3067\u3059
+jsp.error.unable.to.open.TLD=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u8a18\u8ff0\u5b50 {0} \u3092\u30aa\u30fc\u30d7\u30f3\u3067\u304d\u307e\u305b\u3093
+jsp.buffer.size.zero=\u30d0\u30c3\u30d5\u30a1\u30b5\u30a4\u30ba\u304c0\u4ee5\u4e0b\u3067\u3059
+jsp.error.file.not.found=JSP \u30d5\u30a1\u30a4\u30eb \"{0}\" \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.message.copyinguri={0} \u3092 {1} \u306b\u30b3\u30d4\u30fc\u3057\u307e\u3059
+jsp.message.htmlcomment=\n\u524a\u9664\u3059\u308b\u30b3\u30e1\u30f3\u30c8: \t{0}
+jsp.message.handling_directive=\n\u51e6\u7406\u3059\u308b\u6307\u793a\u5b50: {0}\t{1}
+jsp.message.handling_plugin=\nPlugin: {0}
+jsp.message.package_name_is=\u30d1\u30c3\u30b1\u30fc\u30b8\u540d: {0}
+jsp.message.class_name_is=\u30af\u30e9\u30b9\u540d: {0}
+jsp.message.java_file_name_is=Java\u30d5\u30a1\u30a4\u30eb\u540d: {0}
+jsp.message.class_file_name_is=\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u540d: {0}
+jsp.message.accepted={1} \u3067 {0} \u3092\u53d7\u3051\u5165\u308c\u307e\u3059
+jsp.message.adding_jar=jar {0} \u3092\u30af\u30e9\u30b9\u30d1\u30b9\u306b\u8ffd\u52a0\u3057\u307e\u3059
+jsp.message.compiling_with={0} \u3092\u30b3\u30f3\u30d1\u30a4\u30eb\u4e2d\u3067\u3059
+jsp.message.template_text=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c6\u30ad\u30b9\u30c8
+jsp.error.missing_attribute=TLD\u53c8\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u306b\u3088\u308b\u3068\u3001\u5c5e\u6027 {0} \u306f\u30bf\u30b0 {1} \u306b\u306f\u5fc5\u9808\u3067\u3059
+jsp.error.bad_attribute=TLD\u306b\u3088\u308b\u3068\u3001\u30bf\u30b0 {1} \u306e\u5c5e\u6027 {0} \u306f\u7121\u52b9\u3067\u3059
+jsp.error.tld.unable_to_read=JAR\u30d5\u30a1\u30a4\u30eb \"{0}\" \u304b\u3089TLD \"{1}\" \u3092\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093: {2}
+jsp.error.tld.unable_to_get_jar=TLD\u3092\u542b\u3080JAR\u30ea\u30bd\u30fc\u30b9 \"{0}\" \u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093 : {1}
+jsp.error.tld.missing_jar=TLD\u3092\u542b\u3080JAR\u30ea\u30bd\u30fc\u30b9 \"{0}\" \u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.webxml_not_found=web.xml\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.cmd_line.usage=\u4f7f\u7528\u6cd5:  [-dd <\u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u30d1\u30b9>] [-keepgenerated] \
+<.jsp\u30d5\u30a1\u30a4\u30eb\u7fa4>
+jsp.message.cp_is=\u30af\u30e9\u30b9\u30d1\u30b9 {0} \u306f {1} \u3067\u3059
+jsp.error.unable.to_load_taghandler_class=\u30bf\u30b0\u30cf\u30f3\u30c9\u30e9\u30af\u30e9\u30b9 {0} \u3092 {1} \u306e\u305f\u3081\u306b\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.to_find_method=\u5c5e\u6027 {0} \u306esetter\u30e1\u30bd\u30c3\u30c9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.unable.to_convert_string=\u5c5e\u6027 {1}\u306e\u6587\u5b57\u5217\u3092 {0}\u306b\u5909\u63db\u3067\u304d\u307e\u305b\u3093
+jsp.error.unable.to_introspect=\u30bf\u30b0\u30cf\u30f3\u30c9\u30e9\u30af\u30e9\u30b9 {0} \u3092 {1} \u306e\u305f\u3081\u306b\u5185\u7701\u3067\u304d\u307e\u305b\u3093
+jsp.error.bad_tag=\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9 {1}\u3067\u30a4\u30f3\u30dd\u30fc\u30c8\u3055\u308c\u305f\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u306b\u306f\u3001\u30bf\u30b0 {0} \u306f\u5b58\u5728\u3057\u307e\u305b\u3093
+jsp.error.xml.bad_tag=URI \"{1}\" \u306b\u95a2\u9023\u3065\u3051\u3089\u308c\u305f\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u306e\u4e2d\u306b\u306f\u30bf\u30b0 \"{0}\" \u306f\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.bad_string_Character=\u9577\u30550\u306e\u914d\u5217\u304b\u3089\u306f\u6587\u5b57\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093
+jsp.error.bad_string_char=\u9577\u30550\u306e\u914d\u5217\u304b\u3089\u306f\u6587\u5b57\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093
+jsp.warning.compiler.class.cantcreate=\u6307\u5b9a\u3055\u308c\u305f\u30b3\u30f3\u30d1\u30a4\u30e9\u30d7\u30e9\u30b0\u30a4\u30f3\u30af\u30e9\u30b9 {0} \u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092 {1} \u306e\u305f\u3081\u306b\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u3092Sun\u306eJava\u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u3057\u307e\u3059\u3002
+jsp.warning.compiler.class.notfound=\u6307\u5b9a\u3055\u308c\u305f\u30b3\u30f3\u30d1\u30a4\u30e9\u30d7\u30e9\u30b0\u30a4\u30f3\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002not found. \u30c7\u30d5\u30a9\u30eb\u30c8\u3092Sun\u306eJava\u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u3057\u307e\u3059\u3002
+jsp.warning.compiler.path.notfound=\u6307\u5b9a\u3055\u308c\u305f\u30b3\u30f3\u30d1\u30a4\u30e9\u30d1\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u30b7\u30b9\u30c6\u30e0\u306ePATH\u3092\u30c7\u30d5\u30a9\u30eb\u30c8\u306b\u3057\u307e\u3059\u3002
+jsp.error.jspc.uriroot_not_dir=-uriroot \u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u3001\u65e2\u306b\u5b58\u5728\u3059\u308b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspc.missingTarget=\u30bf\u30fc\u30b2\u30c3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093: -webapp\u53c8\u306f-uriroot\uff0c\u53c8\u306f\u4e00\u3064\u4ee5\u4e0a\u306eJSP\u30da\u30fc\u30b8\u3092\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspc.no_uriroot=uriroot\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u306e\u3067\u3001\u6307\u5b9a\u3055\u308c\u305fJSP\u30d5\u30a1\u30a4\u30eb(\u7fa4)\u3092\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+jspc.implicit.uriRoot=uriRoot\u306f\u30c7\u30d5\u30a9\u30eb\u30c8"{0}"\u306b\u8a2d\u5b9a\u3055\u308c\u307e\u3059
+jspc.usage=\u4f7f\u7528\u6cd5: jspc <options> [--] <jsp files>\n\
+JSP\u30d5\u30a1\u30a4\u30eb\u306e\u5834\u6240\u306f\u6b21\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6307\u5b9a\u3059\u308b\u304b\u3001\n\
+\    -webapp <dir>   web-app\u3092\u542b\u3080\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3002\u3059\u3079\u3066\u306eJSP\u30d5\u30a1\u30a4\u30eb\u306f\n\
+\                    \u518d\u5e30\u7684\u306b\u89e3\u6790\u3055\u308c\u308b\n\
+\u53c8\u306f\u6b21\u306e\u4efb\u610f\u306e\u6570\u306e\u30d5\u30a1\u30a4\u30eb\u3067\u6307\u5b9a\u3057\u307e\u3059\u3002\n\
+\    <file>          JSP\u3068\u3057\u3066\u89e3\u6790\u3055\u308c\u308b\u30d5\u30a1\u30a4\u30eb\n\
+\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u4ee5\u4e0b\u306e\u901a\u308a\u3067\u3059\n\
+\    -help           \u3053\u306e\u30d8\u30eb\u30d7\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u8868\u793a\n\
+\    -v              Verbose\u30e2\u30fc\u30c9\n\
+\    -d <dir>        \u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\n\
+\    -l              \u5931\u6557\u3057\u305fJSP\u30da\u30fc\u30b8\u306e\u540d\u524d\u306e\u51fa\u529b\n\
+\    -s              \u6210\u529f\u3057\u305fJSP\u30da\u30fc\u30b8\u306e\u540d\u524d\u306e\u51fa\u529b\n\
+\    -p <name>       \u30bf\u30fc\u30b2\u30c3\u30c8\u30d1\u30c3\u30b1\u30fc\u30b8\u306e\u540d\u524d  (\u30c7\u30d5\u30a9\u30eb\u30c8\u306forg.apache.jsp)\n\
+\    -c <name>       \u30bf\u30fc\u30b2\u30c3\u30c8\u30af\u30e9\u30b9\u306e\u540d\u524d (\u6700\u521d\u306eJSP\u30da\u30fc\u30b8\u3060\u3051\u306b\u9069\u7528\u3055\u308c\u308b)\n\
+\    -mapped         JSP\u306e\u5404HTML\u884c\u3054\u3068\u306bwrite()\u30b3\u30fc\u30eb\u3092\u751f\u6210\n\
+\    -die[#]         \u81f4\u547d\u7684\u30a8\u30e9\u30fc\u306b\u30a8\u30e9\u30fc\u30ea\u30bf\u30fc\u30f3\u30b3\u30fc\u30c9(#)\u3092\u751f\u6210 (\u30c7\u30d5\u30a9\u30eb\u30c8\u306f1)\n\
+\    -uribase <dir>  \u30b3\u30f3\u30d1\u30a4\u30eb\u304c\u76f8\u5bfe\u7684\u306b\u304a\u3053\u306a\u308f\u308c\u308buri\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\n\
+\                    (\u30c7\u30d5\u30a9\u30eb\u30c8\u306f"/")\n\
+\    -uriroot <dir>  -webapp\u3068\u540c\u3058\n\
+\    -compile        \u751f\u6210\u3057\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u30b3\u30f3\u30d1\u30a4\u30eb\n\
+\    -webinc <file>  \u30d5\u30a1\u30a4\u30eb\u306b\u90e8\u5206\u7684\u306a\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30de\u30c3\u30d4\u30f3\u30b0\u3092\u4f5c\u6210\n\
+\    -webxml <file>  \u30d5\u30a1\u30a4\u30eb\u306b\u5b8c\u5168\u306aweb.xml\u3092\u4f5c\u6210\n\
+\    -ieplugin <clsid>  Internet Explorer\u306eJava Plugin\u306eclassid\n\
+\    -classpath <path>  java.class.path\u30b7\u30b9\u30c6\u30e0\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u4e0a\u66f8\u304d\n\
+\    -xpoweredBy        X-Powered-By\u30ec\u30b9\u30dd\u30f3\u30b9\u30d8\u30c3\u30c0\u306e\u8ffd\u52a0\n\
+\    -trimSpaces        \u30a2\u30af\u30b7\u30e7\u30f3\u3084\u6307\u793a\u5b50\u306e\u9593\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c6\u30ad\u30b9\u30c8\u4e2d\u306e\u30b9\u30da\u30fc\u30b9\u3092\u524a\u9664\n\
+\    -trimSpaces        Trim spaces in template text between actions, directives\n\
+\    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.6)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.6)\n\
+
+jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
+\n\
+<!DOCTYPE web-app\n\
+\    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"\n\
+\    "http://java.sun.com/dtd/web-app_2_3.dtd">\n\
+<!--\n\
+Automatically created by Apache Tomcat JspC.\n\
+-->\n\
+<web-app>\n\
+\n
+jspc.webxml.footer=\n\
+</web-app>\n\
+\n
+jspc.webinc.header=\n\
+<!--\n\
+Automatically created by Apache Tomcat JspC.\n\
+Place this fragment in the web.xml before all icon, display-name,\n\
+description, distributable, and context-param elements.\n\
+-->\n
+jspc.webinc.footer=\n\
+<!--\n\
+All session-config, mime-mapping, welcome-file-list, error-page, taglib,\n\
+resource-ref, security-constraint, login-config, security-role,\n\
+env-entry, and ejb-ref elements should follow this fragment.\n\
+-->\n
+jspc.webinc.insertEnd=<!-- JSPC servlet mappings end -->
+jspc.webinc.insertStart=<!-- JSPC servlet mappings start -->
+jspc.error.jasperException=\u30a8\u30e9\u30fc: \u30d5\u30a1\u30a4\u30eb ''{0}'' \u306f\u6b21\u306e\u4f8b\u5916\u3092\u767a\u751f\u3057\u307e\u3057\u305f: {1}
+jspc.error.generalException=\u30a8\u30e9\u30fc: \u30d5\u30a1\u30a4\u30eb ''{0}'' \u306f\u6b21\u306e\u4f8b\u5916\u3092\u767a\u751f\u3057\u307e\u3057\u305f:
+jspc.error.fileDoesNotExist=\u30d5\u30a1\u30a4\u30eb\u5f15\u6570 ''{0}'' \u306f\u5b58\u5728\u3057\u307e\u305b\u3093\u3002
+jspc.error.emptyWebApp=-webapp\u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u306f\u3001\u30d5\u30a1\u30a4\u30eb\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.library.invalid=\u30e9\u30a4\u30d6\u30e9\u30ea{0}\u306b\u5f93\u3046\u3068JSP\u30da\u30fc\u30b8\u306f\u7121\u52b9\u3067\u3059: {1}
+jsp.error.tlvclass.instantiation=TagLibraryValidator\u30af\u30e9\u30b9\u306e\u30ed\u30fc\u30c9\u53c8\u306f\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u5316\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+jsp.error.tlv.invalid.page={0} \u306b\u5bfe\u3059\u308bTagLibraryValidator\u306e\u691c\u8a3c\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u3067\u3059 ({1})
+jsp.error.tei.invalid.attributes={0} \u306b\u5bfe\u3059\u308bTagExtraInfo\u304b\u3089\u306e\u691c\u8a3c\u30a8\u30e9\u30fc\u30e1\u30c3\u30bb\u30fc\u30b8\u3067\u3059
+jsp.parser.sax.propertynotsupported=SAX\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.parser.sax.propertynotrecognized=SAX\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u8a8d\u8b58\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.parser.sax.featurenotsupported=SAX\u30d5\u30a3\u30fc\u30c1\u30e3\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.parser.sax.featurenotrecognized=SAX\u30d5\u30a3\u30fc\u30c1\u30e3\u304c\u8a8d\u8b58\u3055\u308c\u307e\u305b\u3093: {0}
+jsp.error.no.more.content=\u5fc5\u8981\u306a\u89e3\u6790\u4e2d\u306b\u5185\u5bb9\u306e\u6700\u5f8c\u307e\u3067\u9054\u3057\u307e\u3057\u305f: \u30bf\u30b0\u306e\u30cd\u30b9\u30c8\u306e\u30a8\u30e9\u30fc\u304b\u3082\u3057\u308c\u307e\u305b\u3093
+jsp.error.parse.xml=\u30d5\u30a1\u30a4\u30eb{0}\u306eXML\u89e3\u6790\u30a8\u30e9\u30fc
+jsp.error.parse.xml.line=\u30d5\u30a1\u30a4\u30eb{0}\u306eXML\u89e3\u6790\u30a8\u30e9\u30fc: (\u884c {1}, \u5217 {2})
+jsp.error.parse.xml.scripting.invalid.body={0} \u8981\u7d20\u306e\u30dc\u30c7\u30a3\u306fXML\u8981\u7d20\u3092\u542b\u3093\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.internal.tldinit=TldLocationsCache\u3092\u521d\u671f\u5316\u4e2d\u306e\u4f8b\u5916\u3067\u3059: {0}
+jsp.error.internal.filenotfound=\u5185\u90e8\u30a8\u30e9\u30fc: \u30d5\u30a1\u30a4\u30eb {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.internal.evaluator_not_found=\u5185\u90e8\u30a8\u30e9\u30fc: \u5f0f\u691c\u8a3c\u5668\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+jsp.error.parse.xml.invalidPublicId=\u7121\u52b9\u306aPUBLIC ID: {0}
+jsp.error.include.flush.invalid.value=flush\u5c5e\u6027\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059: {0}
+jsp.error.unsupported.encoding=\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u306a\u3044\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u3067\u3059: {0}
+tld.error.variableNotAllowed=null\u3067\u306a\u3044\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3092\u8fd4\u3059TagExtraInfo\u3092\u6301\u3064\u4e00\u3064\u4ee5\u4e0a\u306e\u5909\u6570\u526f\u8981\u7d20\u3092\u6301\u3064\u30bf\u30b0\u306f\u30a8\u30e9\u30fc\u3067\u3059\u3002
+jsp.error.tldInWebDotXmlNotFound=web.xml\u3067\u6307\u5b9a\u3055\u308c\u305fURI {0} \u306bTLD\u3092\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+jsp.error.taglibDirective.absUriCannotBeResolved=\u7d76\u5bfeURI:  {0} \u306fweb.xml\u3068\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u305fJAR\u30d5\u30a1\u30a4\u30eb\u306e\u3069\u3061\u3089\u304b\u3067\u3082\u89e3\u6c7a\u3067\u304d\u307e\u305b\u3093
+jsp.error.taglibDirective.missing.location=taglib\u6307\u793a\u5b50\u306e\u4e2d\u306b'uri'\u5c5e\u6027\u3068'tagdir'\u5c5e\u6027\u306e\u3069\u3061\u3089\u3082\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.taglibDirective.both_uri_and_tagdir=\'uri\'\u5c5e\u6027 \u3068 \'tagdir\'\u5c5e\u6027\u306e\u4e21\u65b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.invalid.tagdir=\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u304c\"/WEB-INF/tags\"\u3067\u59cb\u307e\u308a\u307e\u305b\u3093
+jsp.error.unterminated.user.tag=\u672a\u7d42\u4e86\u306e\u30e6\u30fc\u30b6\u5b9a\u7fa9\u30bf\u30b0: \u7d42\u4e86\u30bf\u30b0 {0} \u304c\u898b\u3064\u304b\u3089\u306a\u3044\u304b\u3001\u30cd\u30b9\u30c8\u304c\u9593\u9055\u3063\u3066\u3044\u307e\u3059
+#jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
+jspx.error.templateDataNotInJspCdata=\u8a3c\u660e\u30a8\u30e9\u30fc: \u8981\u7d20&lt;{0}&gt;\u306f\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c7\u30fc\u30bf\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c7\u30fc\u30bf\u306f\u3001&lt;jsp:text&gt;\u8981\u7d20\u306e\u4e2d\u3067\u96a0\u853d\u3055\u308c\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093\u3002[JSP1.2 PFD 5.1.9]\n\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c7\u30fc\u30bf\u306e\u30a8\u30e9\u30fc\u3067\u3059: {1}
+#Error while processing taglib jar file {0}: {1}
+jsp.error.taglib.reserved.prefix=taglib\u30d7\u30ea\u30d5\u30a3\u30af\u30b9 {0} \u306f\u4e88\u7d04\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.invalid.javaEncoding=\u7121\u52b9\u306aJava\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u3067\u3059\u3002{0}\u3092\u8a66\u3057\u3066\u3001\u305d\u308c\u304b\u3089{1}\u3092\u8a66\u3057\u307e\u3057\u305f\u304c\u3001\u4e21\u65b9\u304c\u5931\u6557\u3057\u307e\u3057\u305f
+jsp.error.needAlternateJavaEncoding=\u30c7\u30d5\u30a9\u30eb\u30c8\u306eJava\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 {0} \u306f\u3042\u306a\u305f\u306e\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u3067\u306f\u7121\u52b9\u3067\u3059\u3002JspServlet\u306e 'javaEncoding' \u30d1\u30e9\u30e1\u30bf\u3067\u3001\u5225\u306e\u5024\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002
+#Error when compiling, used for jsp line number error messages
+jsp.error.single.line.number=JSP\u30d5\u30a1\u30a4\u30eb: {1} \u306e\u4e2d\u306e{0}\u884c\u76ee\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+jsp.error.multiple.line.number=\n\nJPS\u30d5\u30a1\u30a4\u30eb: {2}\u306e\u4e2d\u306e{0}\u884c\u76ee\u3068{1}\u884c\u76ee\u306e\u9593\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\n\n
+jsp.error.corresponding.servlet=\u751f\u6210\u3055\u308c\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u30a8\u30e9\u30fc\u3067\u3059:\n
+jsp.error.empty.body.not.allowed={0} \u306b\u5bfe\u3057\u3066\u7a7a\u306e\u30dc\u30c7\u30a3\u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.jspbody.required=jsp:attribute\u304c\u4f7f\u7528\u3055\u308c\u305f\u5834\u5408\u306b\u306f\u3001{0}\u306b\u30bf\u30b0\u30dc\u30c7\u30a3\u3092\u6307\u5b9a\u3059\u308b\u305f\u3081\u306bjsp:body\u3092\u4f7f\u7528\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspbody.emptybody.only={0} \u30bf\u30b0\u306f\u3001\u305d\u306e\u30dc\u30c7\u30a3\u4e2d\u306bjsp:attribute\u3060\u3051\u3092\u6301\u3064\u3053\u3068\u304c\u3067\u304d\u307e\u3059
+jsp.error.no.scriptlets=\u30b9\u30af\u30ea\u30d7\u30c6\u30a3\u30f3\u30b0\u8981\u7d20 ( &lt;%!\u3001&lt;jsp:declaration\u3001&lt;%=\u3001&lt;jsp:expression\u3001&lt;%\u3001&lt;jsp:scriptlet ) \u306f\u3053\u3053\u3067\u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.internal.unexpected_node_type=\u5185\u90e8\u30a8\u30e9\u30fc: \u672a\u77e5\u306e\u30ce\u30fc\u30c9\u30bf\u30a4\u30d7\u304c\u8868\u308c\u307e\u3057\u305f
+jsp.error.tld.fn.invalid.signature=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}
+jsp.error.tld.fn.duplicate.name=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea {1} \u306e\u4e2d\u306e\u95a2\u6570\u540d {0} \u304c\u91cd\u8907\u3057\u3066\u3044\u307e\u3059
+jsp.error.tld.fn.invalid.signature.commaexpected=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u30b3\u30f3\u30de ',' \u304c\u3042\u308a\u307e\u305b\u3093\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}\u3002
+jsp.error.tld.fn.invalid.signature.parenexpected=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u62ec\u5f27 '(' \u304c\u3042\u308a\u307e\u305b\u3093\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}\u3002
+jsp.error.tld.mandatory.element.missing=\u5fc5\u9808TLD\u8981\u7d20\u304c\u306a\u3044\u3001\u53c8\u306f\u7a7a\u3067\u3059: {0}
+jsp.error.dynamic.attributes.not.implemented={0} \u30bf\u30b0\u306f\u305d\u308c\u304cdynamic\u5c5e\u6027\u3092\u53d7\u3051\u4ed8\u3051\u308b\u3068\u5ba3\u8a00\u3057\u3066\u3044\u307e\u3059\u304c\u3001\u305d\u308c\u306b\u5fc5\u8981\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u5b9f\u88c5\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.nomatching.fragment=attribute\u6307\u793a\u5b50 (name={0}\u304a\u3088\u3073fragment=true\u3092\u6301\u3064) \u304cfragment\u6307\u793a\u5b50\u3088\u308a\u524d\u306b\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.attribute.noequal=\u7b49\u53f7\u8a18\u53f7\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.attribute.noquote=\u5f15\u7528\u7b26\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.attribute.unterminated={0} \u306e\u5c5e\u6027\u304c\u6b63\u3057\u304f\u7d42\u4e86\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.missing.tagInfo={0} \u306b\u5bfe\u3059\u308bTagInfo\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u304cTLD\u304b\u3089\u5931\u308f\u308c\u307e\u3057\u305f
+jsp.error.fragmentwithtype='fragment'\u5c5e\u6027\u3068'type'\u5c5e\u6027\u3092\u4e21\u65b9\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002'fragment'\u304c\u5b58\u5728\u3059\u308b\u5834\u5408\u306b\u306f'type'\u306f'javax.servlet.jsp.tagext.JspFragment'\u306b\u56fa\u5b9a\u3055\u308c\u307e\u3059
+jsp.error.fragmentwithrtexprvalue='fragment'\u5c5e\u6027\u3068'rtexprvalue'\u5c5e\u6027\u3092\u4e21\u65b9\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002'fragment'\u304c\u5b58\u5728\u3059\u308b\u5834\u5408\u306b\u306f'rtexprvalue'\u306f'true'\u306b\u56fa\u5b9a\u3055\u308c\u307e\u3059
+jsp.error.fragmentWithDeclareOrScope='fragment'\u5c5e\u6027\u3068'declare'\u5c5e\u6027\u306e\u4e21\u65b9\u53c8\u306f'scope'\u5c5e\u6027\u304cvariable\u6307\u793a\u5b50\u4e2d\u3067\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+jsp.error.var_and_varReader=\'var\'\u53c8\u306f\'varReader\'\u306e\u3069\u3061\u3089\u304b\u4e00\u3064\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059
+jsp.error.missing_var_or_varReader=\'var\'\u53c8\u306f\'varReader\'\u5c5e\u6027\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.warning.bad.urlpattern.propertygroup=web.xml\u4e2d\u306eurl-pattern\u526f\u8981\u7d20\u4e2d\u306b\u8aa4\u3063\u305f\u5024 {0} \u304c\u3042\u308a\u307e\u3059
+jsp.error.unknown_attribute_type=\u5c5e\u6027 {0} \u306b\u5bfe\u3059\u308b\u672a\u77e5\u306e\u5c5e\u6027\u30bf\u30a4\u30d7\u3067\u3059
+jsp.error.jspelement.missing.name=\u5fc5\u9808\u306eXML\u30b9\u30bf\u30a4\u30eb\u306e'name'\u5c5e\u6027\u304cjsp:element\u4e2d\u306b\u3042\u308a\u307e\u305b\u3093
+jsp.error.xmlns.redefinition.notimplemented=\u5185\u90e8\u30a8\u30e9\u30fc: xmlns:{0}\u3092\u518d\u5b9a\u7fa9\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f\u3002\u540d\u524d\u7a7a\u9593\u306e\u518d\u5b9a\u7fa9\u306f\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+jsp.error.could.not.add.taglibraries=1\u3064\u4ee5\u4e0a\u306e\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093
+jsp.error.duplicate.name.jspattribute=\u6a19\u6e96\u53c8\u306f\u30ab\u30b9\u30bf\u30e0\u30a2\u30af\u30b7\u30e7\u30f3\u4e2d\u3067\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u5c5e\u6027 {0} \u306f\u305d\u308c\u306b\u56f2\u307e\u308c\u305fjsp:attribute\u4e2d\u306ename\u5c5e\u6027\u306e\u5024\u3068\u3057\u3066\u3082\u8868\u308c\u307e\u3059
+jsp.error.not.in.template=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c6\u30ad\u30b9\u30c8\u30dc\u30c7\u30a3\u4e2d\u3067\u306f {0} \u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.badStandardAction=\u7121\u52b9\u306a\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u3067\u3059
+jsp.error.xml.badStandardAction=\u7121\u52b9\u306a\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u3067\u3059: {0}
+jsp.error.tagdirective.badbodycontent=tag\u6307\u793a\u5b50\u4e2d\u306e\u7121\u52b9\u306abody-content ({0})\u3067\u3059
+jsp.error.simpletag.badbodycontent=\u30af\u30e9\u30b9 {0} \u306eTLD\u306fSimpleTag\u306b\u7121\u52b9\u306abody-content (JSP)\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059
+jsp.error.config_pagedir_encoding_mismatch=jsp-property-group\u4e2d\u306b\u6307\u5b9a\u3055\u308c\u3066\u3044\u308bPage-encoding ({0}) \u304cpage\u6307\u793a\u5b50\u4e2d\u306e\u6307\u5b9a ({1}) \u3068\u9055\u3044\u307e\u3059
+jsp.error.prolog_pagedir_encoding_mismatch=XML\u5c0e\u5165\u90e8\u3067\u6307\u5b9a\u3055\u308c\u305fpage-encoding ({0}) \u304cpage\u6307\u793a\u5b50\u4e2d\u306e\u6307\u5b9a ({1}) \u3068\u9055\u3044\u307e\u3059
+jsp.error.prolog_config_encoding_mismatch=XML\u5c0e\u5165\u90e8\u3067\u6307\u5b9a\u3055\u308c\u305fpage-encoding ({0}) \u304cjsp-property-group\u4e2d\u306e\u6307\u5b9a\u3068\u9055\u3044\u307e\u3059 ({1})
+jsp.error.attribute.custom.non_rt_with_expr=TLD\u53c8\u306f\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u4e2d\u306eattribute\u6307\u793a\u5b50\u306b\u5f93\u3063\u3066\u5c5e\u6027{0}\u306f\u3069\u3093\u306a\u5f0f\u3082\u53d7\u3051\u4ed8\u3051\u307e\u305b\u3093
+jsp.error.attribute.standard.non_rt_with_expr={1} \u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306e {0} \u5c5e\u6027\u306f\u3069\u3093\u306a\u5f0f\u3082\u53d7\u3051\u4ed8\u3051\u307e\u305b\u3093
+jsp.error.scripting.variable.missing_name=\u5c5e\u6027 {0} \u304b\u3089\u30b9\u30af\u30ea\u30d7\u30c8\u5909\u6570\u540d\u3092\u6c7a\u5b9a\u3067\u304d\u307e\u305b\u3093
+jasper.error.emptybodycontent.nonempty=TLD\u306b\u5f93\u3063\u3066\u30bf\u30b0 {0} \u306f\u7a7a\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093\u304c\u3001\u305d\u3046\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+jsp.error.tagfile.nameNotUnique={2}\u884c\u76ee\u306e {0} \u306e\u5024\u3068 {1} \u306e\u5024\u306f\u540c\u3058\u3067\u3059
+jsp.error.tagfile.nameFrom.noAttribute=\u3053\u306ename-from-attribute\u5c5e\u6027\u306e\u5024\u3067\u3042\u308b\u5024 \"{0}\" \u306ename\u5c5e\u6027\u3092\u6301\u3064attribute\u6307\u793a\u5b50\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.tagfile.nameFrom.badAttribute=attribute\u6307\u793a\u5b50 ({1}\u884c\u76ee\u3067\u5ba3\u8a00\u3055\u308c\u3001\u305d\u306ename\u5c5e\u6027\u304c\"{0}\"\u3001\u3053\u306ename-from-attribute\u5c5e\u6027\u306e\u5024) \u306fjava.lang.String\u578b\u306e\"required\" \u3067 \"rtexprvalue\".\u3067\u3042\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.page.noSession=\u30bb\u30c3\u30b7\u30e7\u30f3\u306b\u52a0\u308f\u3063\u3066\u3044\u306a\u3044\u30da\u30fc\u30b8\u306e\u4e2d\u3067\u306f\u30bb\u30c3\u30b7\u30e7\u30f3\u30b9\u30b3\u30fc\u30d7\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u305b\u3093
+jsp.error.usebean.noSession=JSP\u30da\u30fc\u30b8\u304c(page\u6307\u793a\u5b50\u306b\u3088\u308a)\u30bb\u30c3\u30b7\u30e7\u30f3\u4e2d\u3067\u5354\u8abf\u3057\u306a\u3044\u3053\u3068\u3092\u5ba3\u8a00\u3057\u3066\u3044\u308b\u6642\u3001\u30bb\u30c3\u30b7\u30e7\u30f3\u30b9\u30b3\u30fc\u30d7\u3092\u4f7f\u7528\u3059\u308b\u305f\u3081\u306euseBean\u304c\u4e0d\u6b63\u3067\u3059
+jsp.error.xml.encodingByteOrderUnsupported = \u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 \"{0}\" \u306b\u6307\u5b9a\u3055\u308c\u305f\u30d0\u30a4\u30c8\u30aa\u30fc\u30c0\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.xml.encodingDeclInvalid = \u7121\u52b9\u306a\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u540d \"{0}\" \u3067\u3059
+jsp.error.xml.encodingDeclRequired = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u306b\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\u5ba3\u8a00\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.morePseudoAttributes = \u3088\u308a\u591a\u304f\u306e\u7591\u4f3c\u5c5e\u6027\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.noMorePseudoAttributes = \u3053\u308c\u4ee5\u4e0a\u306e\u7591\u4f3c\u5c5e\u6027\u306f\u8a31\u3055\u308c\u307e\u305b\u3093
+jsp.error.xml.versionInfoRequired = XML\u5ba3\u8a00\u306e\u4e2d\u306b\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.xmlDeclUnterminated = XML\u5ba3\u8a00\u306f\"?>\"\u3067\u7d42\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.reservedPITarget = \"[xX][mM][lL]\"\u306b\u4e00\u81f4\u3059\u308b\u51e6\u7406\u547d\u4ee4\u30bf\u30fc\u30b2\u30c3\u30c8\u306f\u8a31\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.xml.spaceRequiredInPI = \u7a7a\u767d\u304c\u51e6\u7406\u547d\u4ee4\u30bf\u30fc\u30b2\u30c3\u30c8\u3068\u30c7\u30fc\u30bf\u306e\u9593\u306b\u5fc5\u8981\u3067\u3059
+jsp.error.xml.invalidCharInContent = \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306e\u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306e\u8981\u7d20\u5185\u5bb9\u306e\u4e2d\u306b\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.spaceRequiredBeforeStandalone = XML\u5ba3\u8a00\u306eencoding\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.sdDeclInvalid = \u30b9\u30bf\u30f3\u30c9\u30a2\u30ed\u30f3\u6587\u66f8\u5ba3\u8a00\u5024\u306f\"yes\"\u53c8\u306f\"no\"\u306e\u3069\u3061\u3089\u304b\u3067\u3042\u308a\u3001\"{0}\"\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.invalidCharInPI = \u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u547d\u4ee4\u51e6\u7406\u4e2d\u306b\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.versionNotSupported = XML\u30d0\u30fc\u30b8\u30e7\u30f3 \"{0}\" \u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3001XML 1.0\u3060\u3051\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u3059
+jsp.error.xml.pseudoAttrNameExpected = \u7591\u4f3c\u5c5e\u6027\u540d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.expectedByte ={1}\u30d0\u30a4\u30c8UTF-8\u30b7\u30fc\u30b1\u30f3\u30b9\u306e\u30d0\u30a4\u30c8 {0} \u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.invalidByte = {1}\u30d0\u30a4\u30c8UTF-8\u30b7\u30fc\u30b1\u30f3\u30b9\u306e\u7121\u52b9\u306a\u30d0\u30a4\u30c8 {0} \u3067\u3059
+jsp.error.xml.operationNotSupported = {1} reader\u306f\u64cd\u4f5c \"{0}\" \u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u305b\u3093
+jsp.error.xml.invalidHighSurrogate = UTF-8\u30b7\u30fc\u30b1\u30f3\u30b9\u306e\u30cf\u30a4\u30b5\u30ed\u30b2\u30fc\u30c8\u30d3\u30c3\u30c8\u306f0x10\u3092\u8d8a\u3048\u3066\u306f\u3044\u3051\u307e\u305b\u3093\u304c\u30010x{0}\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.invalidASCII = \u30d0\u30a4\u30c8 \"{0}\" \u306f7\u30d3\u30c3\u30c8ASCII\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl = XML\u5ba3\u8a00\u306eencoding\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u306eencoding\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.spaceRequiredBeforeVersionInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u306eversion\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl = XML\u5ba3\u8a00\u306eversion\u7591\u4f3c\u5c5e\u6027\u306e\u524d\u306b\u7a7a\u767d\u304c\u5fc5\u8981\u3067\u3059
+jsp.error.xml.eqRequiredInXMLDecl = XML\u5ba3\u8a00\u4e2d\u3067\"{0}\"\u306e\u6b21\u306b'' = '' \u6587\u5b57\u304c\u7d9a\u304b\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.eqRequiredInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u3067\"{0}\"\u306e\u6b21\u306b'' = ''\u6587\u5b57\u304c\u7d9a\u304b\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.quoteRequiredInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306f\u30af\u30aa\u30fc\u30c8\u3067\u56f2\u307e\u308c\u305f\u6587\u5b57\u5217\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.quoteRequiredInXMLDecl = XML\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306f\u30af\u30aa\u30fc\u30c8\u3067\u56f2\u307e\u308c\u305f\u6587\u5b57\u5217\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.xml.invalidCharInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u306e\u4e2d\u306b\u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.invalidCharInXMLDecl = XML\u5ba3\u8a00\u306e\u4e2d\u306b\u7121\u52b9\u306aXML\u6587\u5b57 (Unicode: 0x{0}) \u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f
+jsp.error.xml.closeQuoteMissingInTextDecl = \u30c6\u30ad\u30b9\u30c8\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306e\u4e2d\u306e\u6700\u5f8c\u306e\u30af\u30aa\u30fc\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.xml.closeQuoteMissingInXMLDecl = XML\u5ba3\u8a00\u4e2d\u306e\"{0}\"\u306b\u7d9a\u304f\u5024\u306e\u4e2d\u306e\u6700\u5f8c\u306e\u30af\u30aa\u30fc\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.multiple.jsp = \u8907\u6570\u306e\u4ed5\u69d8\u3092\u6e80\u305f\u3059\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.jspoutput.conflict=&lt;jsp:output&gt;: \"{0}\"\u306b\u7570\u306a\u308b\u5024\u3092\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {1}, \u65b0: {2})
+jsp.error.jspoutput.doctypenamesystem=&lt;jsp:output&gt;: 'doctype-root-element' \u53ca\u3073 'doctype-system' \u5c5e\u6027\u306f\u540c\u6642\u306b\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspoutput.doctypepulicsystem=&lt;jsp:output&gt;: 'doctype-public'\u5c5e\u6027\u3092\u6307\u5b9a\u3059\u308b\u5834\u5408\u306f\u3001'doctype-system' \u5c5e\u6027\u3082\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspoutput.nonemptybody=&lt;jsp:output&gt; \u30dc\u30c7\u30a3\u3092\u6301\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.jspoutput.invalidUse=&lt;jsp:output&gt; \u6a19\u6e96\u69cb\u6587\u306e\u4e2d\u3067\u4f7f\u7528\u3057\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.attributes.not.allowed = {0} \u306f\u5c5e\u6027\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.tagfile.badSuffix=\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9 {0} \u306e\u4e2d\u306b\".tag\" \u62e1\u5f35\u5b50\u304c\u3042\u308a\u307e\u305b\u3093
+jsp.error.tagfile.illegalPath=\u4e0d\u6b63\u306a\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9\u3067\u3059: {0}\u3001\u3053\u308c\u306f\"/WEB-INF/tags\"\u53c8\u306f\"/META-INF/tags\"\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.plugin.wrongRootElement={0} \u306e\u4e2d\u306e\u30eb\u30fc\u30c8\u8981\u7d20\u306e\u540d\u524d\u306f {1} \u3067\u306f\u3042\u308a\u307e\u305b\u3093
+jsp.error.attribute.invalidPrefix=\u5c5e\u6027\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9 {0} \u306f\u3069\u306e\u53d6\u308a\u8fbc\u307e\u308c\u305f\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea\u306b\u3082\u5bfe\u5fdc\u3057\u307e\u305b\u3093
+jsp.error.nested.jspattribute=jsp:attribute\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u5225\u306ejsp:attribute\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u7bc4\u56f2\u5185\u3067\u30cd\u30b9\u30c8\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.nested.jspbody=jsp:body\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u5225\u306ejsp:body\u53c8\u306fjsp:attribute\u6a19\u6e96\u30a2\u30af\u30b7\u30e7\u30f3\u306e\u7bc4\u56f2\u5185\u3067\u30cd\u30b9\u30c8\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.variable.either.name=name-given\u53c8\u306fname-from-attribute\u5c5e\u6027\u306e\u3069\u3061\u3089\u304b\u3092variable\u6307\u793a\u5b50\u306e\u4e2d\u3067\u6307\u5b9a\u3055\u308c\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.variable.both.name=variable\u6307\u793a\u5b50\u4e2d\u3067name-given\u3068name-from-attribute\u5c5e\u6027\u306e\u4e21\u65b9\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+jsp.error.variable.alias=name-from-attribute\u304a\u3088\u3073alias\u5c5e\u6027\u306e\u4e21\u65b9\u3092variable\u6307\u793a\u5b50\u4e2d\u306b\u6307\u5b9a\u3059\u308b\u3001\u53c8\u306f\u3069\u3061\u3089\u3082\u6307\u5b9a\u3057\u306a\u3044\u3053\u3068\u304c\u3067\u304d\u307e\u3059
+jsp.error.attribute.null_name=\u7a7a\u306e\u5c5e\u6027\u540d\u3067\u3059
+jsp.error.jsptext.badcontent=\'&lt;\'\u304c&lt;jsp:text&gt;\u306e\u30dc\u30c7\u30a3\u306e\u4e2d\u306b\u73fe\u308c\u308b\u6642\u306f\u3001CDATA\u306e\u4e2d\u306b\u96a0\u853d\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.jsproot.version.invalid=\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u756a\u53f7\u3067\u3059: \"{0}\"\u3001\"1.2\"\u3000\"2.0\"\u3000\"2.1\" \u53c8\u306f \"2.2\"\u3000\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.noFunctionPrefix=\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u540d\u524d\u7a7a\u9593\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u6642\u306b\u306f\u3001\u95a2\u6570 {0} \u306f\u30d7\u30ea\u30d5\u30a3\u30af\u30b9\u4ed8\u304d\u3067\u4f7f\u7528\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+jsp.error.noFunction=\u95a2\u6570 {0} \u3092\u6307\u5b9a\u3055\u308c\u305f\u30d7\u30ea\u30d5\u30a3\u30af\u30b9\u3067\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+jsp.error.noFunctionMethod=\u95a2\u6570 \"{1}\" \u306e\u30e1\u30bd\u30c3\u30c9 \"{0}\" \u304c \"{2}\" \u4e2d\u3067\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+jsp.error.function.classnotfound=TLD\u306e\u4e2d\u3067\u95a2\u6570 {1} \u306b\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {2}
+jsp.error.signature.classnotfound=TLD\u306e\u4e2d\u306e\u30e1\u30bd\u30c3\u30c9\u30b7\u30b0\u30cd\u30c1\u30e3\u3067\u95a2\u6570 {1} \u306b\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 {2}
+jsp.error.text.has_subelement=&lt;jsp:text&gt; \u306f\u526f\u8981\u7d20\u3092\u6301\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+jsp.error.data.file.read=\u30d5\u30a1\u30a4\u30eb \"{0}\" \u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+jsp.error.prefix.refined=\u30d7\u30ea\u30d5\u30a3\u30c3\u30af\u30b9 {0} \u304c\u73fe\u5728\u306e\u30b9\u30b3\u30fc\u30d7\u4e2d\u3067\u65e2\u306b {2} \u3068\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u308b\u306e\u3067 {1} \u306b\u518d\u5b9a\u7fa9\u3057\u307e\u3057\u305f
+jsp.error.nested_jsproot=\u5165\u308c\u5b50\u306b\u306a\u3063\u305f &lt;jsp:root&gt; \u3067\u3059
+jsp.error.unbalanced.endtag=\u7d42\u4e86\u30bf\u30b0 \"&lt;/{0}\" \u306e\u5bfe\u5fdc\u304c\u53d6\u308c\u3066\u3044\u307e\u305b\u3093
+jsp.error.invalid.bean=useBean\u306e\u30af\u30e9\u30b9\u5c5e\u6027 {0} \u306e\u5024\u304c\u7121\u52b9\u3067\u3059
+jsp.error.prefix.use_before_dcl=\u3053\u306e\u30bf\u30b0\u6307\u793a\u5b50\u3067\u6307\u5b9a\u3055\u308c\u3066\u3044\u308b\u30d7\u30ea\u30d5\u30a3\u30c3\u30af\u30b9 {0} \u306f\u3001\u3059\u3067\u306b\u30d5\u30a1\u30a4\u30eb {1} \u306e {2} \u884c\u76ee\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u3067\u4f7f\u7528\u3055\u308c\u3066\u3044\u307e\u3059
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/BodyContentImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/BodyContentImpl.java
new file mode 100644
index 0000000..d9fb0f8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/BodyContentImpl.java
@@ -0,0 +1,657 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.BodyContent;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Write text to a character-output stream, buffering characters so as
+ * to provide for the efficient writing of single characters, arrays,
+ * and strings. 
+ *
+ * Provide support for discarding for the output that has been buffered. 
+ *
+ * @author Rajiv Mordani
+ * @author Jan Luehe
+ */
+public class BodyContentImpl extends BodyContent {
+    
+    private static final String LINE_SEPARATOR = 
+        System.getProperty("line.separator");
+    private static final boolean LIMIT_BUFFER = 
+        Boolean.valueOf(System.getProperty("org.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER", "false")).booleanValue();
+    
+    private char[] cb;
+    private int nextChar;
+    private boolean closed;
+    
+    // Enclosed writer to which any output is written
+    private Writer writer;
+    
+    /**
+     * Constructor.
+     */
+    public BodyContentImpl(JspWriter enclosingWriter) {
+        super(enclosingWriter);
+        bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE;
+        cb = new char[bufferSize];
+        nextChar = 0;
+        closed = false;
+    }
+    
+    /**
+     * Write a single character.
+     */
+    @Override
+    public void write(int c) throws IOException {
+        if (writer != null) {
+            writer.write(c);
+        } else {
+            ensureOpen();
+            if (nextChar >= bufferSize) {
+                reAllocBuff (1);
+            }
+            cb[nextChar++] = (char) c;
+        }
+    }
+    
+    /**
+     * Write a portion of an array of characters.
+     *
+     * <p> Ordinarily this method stores characters from the given array into
+     * this stream's buffer, flushing the buffer to the underlying stream as
+     * needed.  If the requested length is at least as large as the buffer,
+     * however, then this method will flush the buffer and write the characters
+     * directly to the underlying stream.  Thus redundant
+     * <code>DiscardableBufferedWriter</code>s will not copy data
+     * unnecessarily.
+     *
+     * @param cbuf A character array
+     * @param off Offset from which to start reading characters
+     * @param len Number of characters to write
+     */
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        if (writer != null) {
+            writer.write(cbuf, off, len);
+        } else {
+            ensureOpen();
+            
+            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
+                    ((off + len) > cbuf.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            } else if (len == 0) {
+                return;
+            } 
+            
+            if (len >= bufferSize - nextChar)
+                reAllocBuff (len);
+            
+            System.arraycopy(cbuf, off, cb, nextChar, len);
+            nextChar+=len;
+        }
+    }
+    
+    /**
+     * Write an array of characters.  This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     */
+    @Override
+    public void write(char[] buf) throws IOException {
+        if (writer != null) {
+            writer.write(buf);
+        } else {
+            write(buf, 0, buf.length);
+        }
+    }
+    
+    /**
+     * Write a portion of a String.
+     *
+     * @param s String to be written
+     * @param off Offset from which to start reading characters
+     * @param len Number of characters to be written
+     */
+    @Override
+    public void write(String s, int off, int len) throws IOException {
+        if (writer != null) {
+            writer.write(s, off, len);
+        } else {
+            ensureOpen();
+            if (len >= bufferSize - nextChar)
+                reAllocBuff(len);
+            
+            s.getChars(off, off + len, cb, nextChar);
+            nextChar += len;
+        }
+    }
+    
+    /**
+     * Write a string.  This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     */
+    @Override
+    public void write(String s) throws IOException {
+        if (writer != null) {
+            writer.write(s);
+        } else {
+            write(s, 0, s.length());
+        }
+    }
+    
+    /**
+     * Write a line separator.  The line separator string is defined by the
+     * system property <tt>line.separator</tt>, and is not necessarily a single
+     * newline ('\n') character.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    @Override
+    public void newLine() throws IOException {
+        if (writer != null) {
+            writer.write(LINE_SEPARATOR);
+        } else {
+            write(LINE_SEPARATOR);
+        }
+    }
+    
+    /**
+     * 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
+     * @throws IOException
+     */
+    @Override
+    public void print(boolean b) throws IOException {
+        if (writer != null) {
+            writer.write(b ? "true" : "false");
+        } else {
+            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
+     * @throws IOException
+     */
+    @Override
+    public void print(char c) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(c));
+        } else {
+            write(String.valueOf(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
+     * @throws IOException
+     */
+    @Override
+    public void print(int i) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(i));
+        } else {
+            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
+     * @throws IOException
+     */
+    @Override
+    public void print(long l) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(l));
+        } else {
+            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
+     * @throws IOException
+     */
+    @Override
+    public void print(float f) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(f));
+        } else {
+            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
+     * @throws IOException
+     */
+    @Override
+    public void print(double d) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(d));
+        } else {
+            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>
+     * @throws IOException
+     */
+    @Override
+    public void print(char[] s) throws IOException {
+        if (writer != null) {
+            writer.write(s);
+        } else {
+            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
+     * @throws IOException
+     */
+    @Override
+    public void print(String s) throws IOException {
+        if (s == null) s = "null";
+        if (writer != null) {
+            writer.write(s);
+        } else {
+            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
+     * @throws IOException
+     */
+    @Override
+    public void print(Object obj) throws IOException {
+        if (writer != null) {
+            writer.write(String.valueOf(obj));
+        } else {
+            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>).
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println() throws IOException {
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(boolean x) throws IOException {
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(char x) throws IOException {
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(int x) throws IOException {
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(long x) throws IOException {
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(float x) throws IOException {
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(double x) throws IOException{
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(char x[]) throws IOException {
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(String x) throws IOException {
+        print(x);
+        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>.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void println(Object x) throws IOException {
+        print(x);
+        println();
+    }
+    
+    /**
+     * Clear the contents of the buffer. If the buffer has been already
+     * been flushed then the clear operation shall throw an IOException
+     * to signal the fact that some data has already been irrevocably 
+     * written to the client response stream.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    @Override
+    public void clear() throws IOException {
+        if (writer != null) {
+            throw new IOException();
+        } else {
+            nextChar = 0;
+            if (LIMIT_BUFFER && (cb.length > Constants.DEFAULT_TAG_BUFFER_SIZE)) {
+                bufferSize = Constants.DEFAULT_TAG_BUFFER_SIZE;
+                cb = new char[bufferSize];
+            }
+        }
+    }
+    
+    /**
+     * Clears the current contents of the buffer. Unlike clear(), this
+     * method will not throw an IOException if the buffer has already been
+     * flushed. It merely clears the current content of the buffer and
+     * returns.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    @Override
+    public void clearBuffer() throws IOException {
+        if (writer == null) {
+            this.clear();
+        }
+    }
+    
+    /**
+     * Close the stream, flushing it first.  Once a stream has been closed,
+     * further write() or flush() invocations will cause an IOException to be
+     * thrown.  Closing a previously-closed stream, however, has no effect.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    @Override
+    public void close() throws IOException {
+        if (writer != null) {
+            writer.close();
+        } else {
+            closed = true;
+        }
+    }
+    
+    /**
+     * This method returns the size of the buffer used by the JspWriter.
+     *
+     * @return the size of the buffer in bytes, or 0 is unbuffered.
+     */
+    @Override
+    public int getBufferSize() {
+        // According to the spec, the JspWriter returned by 
+        // JspContext.pushBody(java.io.Writer writer) must behave as
+        // though it were unbuffered. This means that its getBufferSize()
+        // must always return 0.
+        return (writer == null) ? bufferSize : 0;
+    }
+    
+    /**
+     * @return the number of bytes unused in the buffer
+     */
+    @Override
+    public int getRemaining() {
+        return (writer == null) ? bufferSize-nextChar : 0;
+    }
+    
+    /**
+     * Return the value of this BodyJspWriter as a Reader.
+     * Note: this is after evaluation!!  There are no scriptlets,
+     * etc in this stream.
+     *
+     * @return the value of this BodyJspWriter as a Reader
+     */
+    @Override
+    public Reader getReader() {
+        return (writer == null) ? new CharArrayReader (cb, 0, nextChar) : null;
+    }
+    
+    /**
+     * Return the value of the BodyJspWriter as a String.
+     * Note: this is after evaluation!!  There are no scriptlets,
+     * etc in this stream.
+     *
+     * @return the value of the BodyJspWriter as a String
+     */
+    @Override
+    public String getString() {
+        return (writer == null) ? new String(cb, 0, nextChar) : null;
+    }
+    
+    /**
+     * Write the contents of this BodyJspWriter into a Writer.
+     * Subclasses are likely to do interesting things with the
+     * implementation so some things are extra efficient.
+     *
+     * @param out The writer into which to place the contents of this body
+     * evaluation
+     */
+    @Override
+    public void writeOut(Writer out) throws IOException {
+        if (writer == null) {
+            out.write(cb, 0, nextChar);
+            // Flush not called as the writer passed could be a BodyContent and
+            // it doesn't allow to flush.
+        }
+    }
+    
+    /**
+     * Sets the writer to which all output is written.
+     */
+    void setWriter(Writer writer) {
+        this.writer = writer;
+        closed = false;
+        if (writer == null) {
+            clearBody();
+        }
+    }
+
+    /**
+     * This method shall "reset" the internal state of a BodyContentImpl,
+     * releasing all internal references, and preparing it for potential
+     * reuse by a later invocation of {@link PageContextImpl#pushBody(Writer)}.
+     *
+     * <p>Note, that BodyContentImpl instances are usually owned by a
+     * PageContextImpl instance, and PageContextImpl instances are recycled
+     * and reused.
+     *
+     * @see PageContextImpl#release()
+     */
+    protected void recycle() {
+        this.writer = null;
+        try {
+            this.clear();
+        } catch (IOException ex) {
+            // ignore
+        }
+    }
+
+    private void ensureOpen() throws IOException {
+        if (closed) throw new IOException("Stream closed");
+    }
+    
+    /**
+     * Reallocates buffer since the spec requires it to be unbounded.
+     */
+    private void reAllocBuff(int len) {
+        
+        if (bufferSize + len <= cb.length) {
+            bufferSize = cb.length;
+            return;
+        }
+        
+        if (len < cb.length) {
+            len = cb.length;
+        }
+        
+        bufferSize = cb.length + len;
+        char[] tmp = new char[bufferSize];
+        
+        System.arraycopy(cb, 0, tmp, 0, cb.length);
+        cb = tmp;
+        tmp = null;
+        
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/HttpJspBase.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/HttpJspBase.java
new file mode 100644
index 0000000..f455833
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/HttpJspBase.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.HttpJspPage;
+
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * This is the super class of all JSP-generated servlets.
+ *
+ * @author Anil K. Vijendran
+ */
+public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
+    
+    private static final long serialVersionUID = 1L;
+
+    protected HttpJspBase() {
+    }
+
+    @Override
+    public final void init(ServletConfig config) 
+        throws ServletException 
+    {
+        super.init(config);
+        jspInit();
+        _jspInit();
+    }
+    
+    @Override
+    public String getServletInfo() {
+        return Localizer.getMessage("jsp.engine.info");
+    }
+
+    @Override
+    public final void destroy() {
+        jspDestroy();
+        _jspDestroy();
+    }
+
+    /**
+     * Entry point into service.
+     */
+    @Override
+    public final void service(HttpServletRequest request, HttpServletResponse response) 
+        throws ServletException, IOException 
+    {
+        _jspService(request, response);
+    }
+    
+    @Override
+    public void jspInit() {
+    }
+
+    public void _jspInit() {
+    }
+
+    @Override
+    public void jspDestroy() {
+    }
+
+    protected void _jspDestroy() {
+    }
+
+    @Override
+    public abstract void _jspService(HttpServletRequest request, 
+                                     HttpServletResponse response) 
+        throws ServletException, IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/InstanceManagerFactory.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/InstanceManagerFactory.java
new file mode 100644
index 0000000..1a277e5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/InstanceManagerFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.runtime;
+
+import javax.servlet.ServletConfig;
+
+import org.apache.tomcat.InstanceManager;
+
+/**
+ * @version $Id: InstanceManagerFactory.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+public class InstanceManagerFactory {
+
+    private InstanceManagerFactory() {
+    }
+
+    public static InstanceManager getInstanceManager(ServletConfig config) {
+        InstanceManager instanceManager = 
+                (InstanceManager) config.getServletContext().getAttribute(InstanceManager.class.getName());
+        if (instanceManager == null) {
+            throw new IllegalStateException("No org.apache.tomcat.InstanceManager set in ServletContext");
+        }
+        return instanceManager;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspApplicationContextImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspApplicationContextImpl.java
new file mode 100644
index 0000000..8f48ff0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspApplicationContextImpl.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.runtime;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.el.ArrayELResolver;
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContextEvent;
+import javax.el.ELContextListener;
+import javax.el.ELResolver;
+import javax.el.ExpressionFactory;
+import javax.el.ListELResolver;
+import javax.el.MapELResolver;
+import javax.el.ResourceBundleELResolver;
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspApplicationContext;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.el.ImplicitObjectELResolver;
+import javax.servlet.jsp.el.ScopedAttributeELResolver;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.el.ELContextImpl;
+
+/**
+ * Implementation of JspApplicationContext
+ * 
+ * @author Jacob Hookom
+ */
+public class JspApplicationContextImpl implements JspApplicationContext {
+
+    private static final String KEY = JspApplicationContextImpl.class.getName();
+
+    private static final ExpressionFactory expressionFactory =
+        ExpressionFactory.newInstance();
+
+    private final List<ELContextListener> contextListeners = new ArrayList<ELContextListener>();
+
+    private final List<ELResolver> resolvers = new ArrayList<ELResolver>();
+
+    private boolean instantiated = false;
+
+    private ELResolver resolver;
+
+    public JspApplicationContextImpl() {
+
+    }
+
+    @Override
+    public void addELContextListener(ELContextListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("ELConextListener was null");
+        }
+        this.contextListeners.add(listener);
+    }
+
+    public static JspApplicationContextImpl getInstance(ServletContext context) {
+        if (context == null) {
+            throw new IllegalArgumentException("ServletContext was null");
+        }
+        JspApplicationContextImpl impl = (JspApplicationContextImpl) context
+                .getAttribute(KEY);
+        if (impl == null) {
+            impl = new JspApplicationContextImpl();
+            context.setAttribute(KEY, impl);
+        }
+        return impl;
+    }
+
+    public ELContextImpl createELContext(JspContext context) {
+        if (context == null) {
+            throw new IllegalArgumentException("JspContext was null");
+        }
+
+        // create ELContext for JspContext
+        final ELResolver r = this.createELResolver();
+        ELContextImpl ctx;
+        if (Constants.IS_SECURITY_ENABLED) {
+            ctx = AccessController.doPrivileged(
+                    new PrivilegedAction<ELContextImpl>() {
+                        @Override
+                        public ELContextImpl run() {
+                            return new ELContextImpl(r);
+                        }
+                    });
+        } else {
+            ctx = new ELContextImpl(r);
+        }
+        ctx.putContext(JspContext.class, context);
+
+        // alert all ELContextListeners
+        ELContextEvent event = new ELContextEvent(ctx);
+        for (int i = 0; i < this.contextListeners.size(); i++) {
+            this.contextListeners.get(i).contextCreated(event);
+        }
+
+        return ctx;
+    }
+
+    private ELResolver createELResolver() {
+        this.instantiated = true;
+        if (this.resolver == null) {
+            CompositeELResolver r = new CompositeELResolver();
+            r.add(new ImplicitObjectELResolver());
+            for (Iterator<ELResolver> itr = this.resolvers.iterator();
+                    itr.hasNext();) {
+                r.add(itr.next());
+            }
+            r.add(new MapELResolver());
+            r.add(new ResourceBundleELResolver());
+            r.add(new ListELResolver());
+            r.add(new ArrayELResolver());    
+            r.add(new BeanELResolver());
+            r.add(new ScopedAttributeELResolver());
+            this.resolver = r;
+        }
+        return this.resolver;
+    }
+
+    @Override
+    public void addELResolver(ELResolver resolver) throws IllegalStateException {
+        if (resolver == null) {
+            throw new IllegalArgumentException("ELResolver was null");
+        }
+        if (this.instantiated) {
+            throw new IllegalStateException(
+                    "cannot call addELResolver after the first request has been made");
+        }
+        this.resolvers.add(resolver);
+    }
+
+    @Override
+    public ExpressionFactory getExpressionFactory() {
+        return expressionFactory;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspContextWrapper.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspContextWrapper.java
new file mode 100644
index 0000000..d7f0927
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspContextWrapper.java
@@ -0,0 +1,497 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.el.ELContext;
+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.http.HttpSession;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.VariableResolver;
+import javax.servlet.jsp.tagext.BodyContent;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.util.Enumerator;
+
+/**
+ * Implementation of a JSP Context Wrapper.
+ * 
+ * The JSP Context Wrapper is a JspContext created and maintained by a tag
+ * handler implementation. It wraps the Invoking JSP Context, that is, the
+ * JspContext instance passed to the tag handler by the invoking page via
+ * setJspContext().
+ * 
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Jacob Hookom
+ */
+public class JspContextWrapper extends PageContext implements VariableResolver {
+
+    // Invoking JSP context
+    private PageContext invokingJspCtxt;
+
+    private transient HashMap<String, Object> pageAttributes;
+
+    // ArrayList of NESTED scripting variables
+    private ArrayList<String> nestedVars;
+
+    // ArrayList of AT_BEGIN scripting variables
+    private ArrayList<String> atBeginVars;
+
+    // ArrayList of AT_END scripting variables
+    private ArrayList<String> atEndVars;
+
+    private Map<String,String> aliases;
+
+    private HashMap<String, Object> originalNestedVars;
+
+    public JspContextWrapper(JspContext jspContext,
+            ArrayList<String> nestedVars, ArrayList<String> atBeginVars,
+            ArrayList<String> atEndVars, Map<String,String> aliases) {
+        this.invokingJspCtxt = (PageContext) jspContext;
+        this.nestedVars = nestedVars;
+        this.atBeginVars = atBeginVars;
+        this.atEndVars = atEndVars;
+        this.pageAttributes = new HashMap<String, Object>(16);
+        this.aliases = aliases;
+
+        if (nestedVars != null) {
+            this.originalNestedVars = new HashMap<String, Object>(nestedVars.size());
+        }
+        syncBeginTagFile();
+    }
+
+    @Override
+    public void initialize(Servlet servlet, ServletRequest request,
+            ServletResponse response, String errorPageURL,
+            boolean needsSession, int bufferSize, boolean autoFlush)
+            throws IOException, IllegalStateException, IllegalArgumentException {
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        return pageAttributes.get(name);
+    }
+
+    @Override
+    public Object getAttribute(String name, int scope) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (scope == PAGE_SCOPE) {
+            return pageAttributes.get(name);
+        }
+
+        return invokingJspCtxt.getAttribute(name, scope);
+    }
+
+    @Override
+    public void setAttribute(String name, Object value) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (value != null) {
+            pageAttributes.put(name, value);
+        } else {
+            removeAttribute(name, PAGE_SCOPE);
+        }
+    }
+
+    @Override
+    public void setAttribute(String name, Object value, int scope) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (scope == PAGE_SCOPE) {
+            if (value != null) {
+                pageAttributes.put(name, value);
+            } else {
+                removeAttribute(name, PAGE_SCOPE);
+            }
+        } else {
+            invokingJspCtxt.setAttribute(name, value, scope);
+        }
+    }
+
+    @Override
+    public Object findAttribute(String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        Object o = pageAttributes.get(name);
+        if (o == null) {
+            o = invokingJspCtxt.getAttribute(name, REQUEST_SCOPE);
+            if (o == null) {
+                if (getSession() != null) {
+                    o = invokingJspCtxt.getAttribute(name, SESSION_SCOPE);
+                }
+                if (o == null) {
+                    o = invokingJspCtxt.getAttribute(name, APPLICATION_SCOPE);
+                }
+            }
+        }
+
+        return o;
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        pageAttributes.remove(name);
+        invokingJspCtxt.removeAttribute(name, REQUEST_SCOPE);
+        if (getSession() != null) {
+            invokingJspCtxt.removeAttribute(name, SESSION_SCOPE);
+        }
+        invokingJspCtxt.removeAttribute(name, APPLICATION_SCOPE);
+    }
+
+    @Override
+    public void removeAttribute(String name, int scope) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (scope == PAGE_SCOPE) {
+            pageAttributes.remove(name);
+        } else {
+            invokingJspCtxt.removeAttribute(name, scope);
+        }
+    }
+
+    @Override
+    public int getAttributesScope(String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (pageAttributes.get(name) != null) {
+            return PAGE_SCOPE;
+        } else {
+            return invokingJspCtxt.getAttributesScope(name);
+        }
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNamesInScope(int scope) {
+        if (scope == PAGE_SCOPE) {
+            return new Enumerator<String>(pageAttributes.keySet().iterator());
+        }
+
+        return invokingJspCtxt.getAttributeNamesInScope(scope);
+    }
+
+    @Override
+    public void release() {
+        invokingJspCtxt.release();
+    }
+
+    @Override
+    public JspWriter getOut() {
+        return invokingJspCtxt.getOut();
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return invokingJspCtxt.getSession();
+    }
+
+    @Override
+    public Object getPage() {
+        return invokingJspCtxt.getPage();
+    }
+
+    @Override
+    public ServletRequest getRequest() {
+        return invokingJspCtxt.getRequest();
+    }
+
+    @Override
+    public ServletResponse getResponse() {
+        return invokingJspCtxt.getResponse();
+    }
+
+    @Override
+    public Exception getException() {
+        return invokingJspCtxt.getException();
+    }
+
+    @Override
+    public ServletConfig getServletConfig() {
+        return invokingJspCtxt.getServletConfig();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return invokingJspCtxt.getServletContext();
+    }
+
+    @Override
+    public void forward(String relativeUrlPath) throws ServletException,
+            IOException {
+        invokingJspCtxt.forward(relativeUrlPath);
+    }
+
+    @Override
+    public void include(String relativeUrlPath) throws ServletException,
+            IOException {
+        invokingJspCtxt.include(relativeUrlPath);
+    }
+
+    @Override
+    public void include(String relativeUrlPath, boolean flush)
+            throws ServletException, IOException {
+        invokingJspCtxt.include(relativeUrlPath, false);
+    }
+
+    @Override
+    @Deprecated
+    public VariableResolver getVariableResolver() {
+        return this;
+    }
+
+    @Override
+    public BodyContent pushBody() {
+        return invokingJspCtxt.pushBody();
+    }
+
+    @Override
+    public JspWriter pushBody(Writer writer) {
+        return invokingJspCtxt.pushBody(writer);
+    }
+
+    @Override
+    public JspWriter popBody() {
+        return invokingJspCtxt.popBody();
+    }
+
+    @Override
+    @Deprecated
+    public ExpressionEvaluator getExpressionEvaluator() {
+        return invokingJspCtxt.getExpressionEvaluator();
+    }
+
+    @Override
+    public void handlePageException(Exception ex) throws IOException,
+            ServletException {
+        // Should never be called since handleException() called with a
+        // Throwable in the generated servlet.
+        handlePageException((Throwable) ex);
+    }
+
+    @Override
+    public void handlePageException(Throwable t) throws IOException,
+            ServletException {
+        invokingJspCtxt.handlePageException(t);
+    }
+
+    /**
+     * VariableResolver interface
+     */
+    @Override
+    @Deprecated
+    public Object resolveVariable(String pName) throws ELException {
+        ELContext ctx = this.getELContext();
+        return ctx.getELResolver().getValue(ctx, null, pName);
+    }
+
+    /**
+     * Synchronize variables at begin of tag file
+     */
+    public void syncBeginTagFile() {
+        saveNestedVariables();
+    }
+
+    /**
+     * Synchronize variables before fragment invocation
+     */
+    public void syncBeforeInvoke() {
+        copyTagToPageScope(VariableInfo.NESTED);
+        copyTagToPageScope(VariableInfo.AT_BEGIN);
+    }
+
+    /**
+     * Synchronize variables at end of tag file
+     */
+    public void syncEndTagFile() {
+        copyTagToPageScope(VariableInfo.AT_BEGIN);
+        copyTagToPageScope(VariableInfo.AT_END);
+        restoreNestedVariables();
+    }
+
+    /**
+     * Copies the variables of the given scope from the virtual page scope of
+     * this JSP context wrapper to the page scope of the invoking JSP context.
+     * 
+     * @param scope
+     *            variable scope (one of NESTED, AT_BEGIN, or AT_END)
+     */
+    private void copyTagToPageScope(int scope) {
+        Iterator<String> iter = null;
+
+        switch (scope) {
+        case VariableInfo.NESTED:
+            if (nestedVars != null) {
+                iter = nestedVars.iterator();
+            }
+            break;
+        case VariableInfo.AT_BEGIN:
+            if (atBeginVars != null) {
+                iter = atBeginVars.iterator();
+            }
+            break;
+        case VariableInfo.AT_END:
+            if (atEndVars != null) {
+                iter = atEndVars.iterator();
+            }
+            break;
+        }
+
+        while ((iter != null) && iter.hasNext()) {
+            String varName = iter.next();
+            Object obj = getAttribute(varName);
+            varName = findAlias(varName);
+            if (obj != null) {
+                invokingJspCtxt.setAttribute(varName, obj);
+            } else {
+                invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE);
+            }
+        }
+    }
+
+    /**
+     * Saves the values of any NESTED variables that are present in the invoking
+     * JSP context, so they can later be restored.
+     */
+    private void saveNestedVariables() {
+        if (nestedVars != null) {
+            Iterator<String> iter = nestedVars.iterator();
+            while (iter.hasNext()) {
+                String varName = iter.next();
+                varName = findAlias(varName);
+                Object obj = invokingJspCtxt.getAttribute(varName);
+                if (obj != null) {
+                    originalNestedVars.put(varName, obj);
+                }
+            }
+        }
+    }
+
+    /**
+     * Restores the values of any NESTED variables in the invoking JSP context.
+     */
+    private void restoreNestedVariables() {
+        if (nestedVars != null) {
+            Iterator<String> iter = nestedVars.iterator();
+            while (iter.hasNext()) {
+                String varName = iter.next();
+                varName = findAlias(varName);
+                Object obj = originalNestedVars.get(varName);
+                if (obj != null) {
+                    invokingJspCtxt.setAttribute(varName, obj);
+                } else {
+                    invokingJspCtxt.removeAttribute(varName, PAGE_SCOPE);
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks to see if the given variable name is used as an alias, and if so,
+     * returns the variable name for which it is used as an alias.
+     * 
+     * @param varName
+     *            The variable name to check
+     * @return The variable name for which varName is used as an alias, or
+     *         varName if it is not being used as an alias
+     */
+    private String findAlias(String varName) {
+
+        if (aliases == null)
+            return varName;
+
+        String alias = aliases.get(varName);
+        if (alias == null) {
+            return varName;
+        }
+        return alias;
+    }
+
+    //private ELContextImpl elContext;
+
+    @Override
+    public ELContext getELContext() {
+        // instead decorate!!!
+        
+        return this.invokingJspCtxt.getELContext();
+        
+        /*
+        if (this.elContext != null) {
+            JspFactory jspFact = JspFactory.getDefaultFactory();
+            ServletContext servletContext = this.getServletContext();
+            JspApplicationContextImpl jspCtx = (JspApplicationContextImpl) jspFact
+                    .getJspApplicationContext(servletContext);
+            this.elContext = jspCtx.createELContext(this);
+        }
+        return this.elContext;
+        */
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspFactoryImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspFactoryImpl.java
new file mode 100644
index 0000000..74a1431
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspFactoryImpl.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.runtime;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.jsp.JspApplicationContext;
+import javax.servlet.jsp.JspEngineInfo;
+import javax.servlet.jsp.JspFactory;
+import javax.servlet.jsp.PageContext;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Implementation of JspFactory.
+ *
+ * @author Anil K. Vijendran
+ */
+public class JspFactoryImpl extends JspFactory {
+
+    // Logger
+    private final Log log = LogFactory.getLog(JspFactoryImpl.class);
+
+    private static final String SPEC_VERSION = "2.1";
+    private static final boolean USE_POOL = 
+        Boolean.valueOf(System.getProperty("org.apache.jasper.runtime.JspFactoryImpl.USE_POOL", "true")).booleanValue();
+    private static final int POOL_SIZE = 
+        Integer.valueOf(System.getProperty("org.apache.jasper.runtime.JspFactoryImpl.POOL_SIZE", "8")).intValue();
+
+    private ThreadLocal<PageContextPool> localPool = new ThreadLocal<PageContextPool>();
+
+    @Override
+    public PageContext getPageContext(Servlet servlet, ServletRequest request,
+            ServletResponse response, String errorPageURL, boolean needsSession,
+            int bufferSize, boolean autoflush) {
+
+        if( Constants.IS_SECURITY_ENABLED ) {
+            PrivilegedGetPageContext dp = new PrivilegedGetPageContext(
+                    this, servlet, request, response, errorPageURL,
+                    needsSession, bufferSize, autoflush);
+            return AccessController.doPrivileged(dp);
+        } else {
+            return internalGetPageContext(servlet, request, response,
+                    errorPageURL, needsSession,
+                    bufferSize, autoflush);
+        }
+    }
+
+    @Override
+    public void releasePageContext(PageContext pc) {
+        if( pc == null )
+            return;
+        if( Constants.IS_SECURITY_ENABLED ) {
+            PrivilegedReleasePageContext dp = new PrivilegedReleasePageContext(
+                    this,pc);
+            AccessController.doPrivileged(dp);
+        } else {
+            internalReleasePageContext(pc);
+        }
+    }
+
+    @Override
+    public JspEngineInfo getEngineInfo() {
+        return new JspEngineInfo() {
+            @Override
+            public String getSpecificationVersion() {
+                return SPEC_VERSION;
+            }
+        };
+    }
+
+    private PageContext internalGetPageContext(Servlet servlet, ServletRequest request,
+            ServletResponse response, String errorPageURL, boolean needsSession,
+            int bufferSize, boolean autoflush) {
+        try {
+            PageContext pc;
+            if (USE_POOL) {
+                PageContextPool pool = localPool.get();
+                if (pool == null) {
+                    pool = new PageContextPool();
+                    localPool.set(pool);
+                }
+                pc = pool.get();
+                if (pc == null) {
+                    pc = new PageContextImpl();
+                }
+            } else {
+                pc = new PageContextImpl();
+            }
+            pc.initialize(servlet, request, response, errorPageURL, 
+                    needsSession, bufferSize, autoflush);
+            return pc;
+        } catch (Throwable ex) {
+            ExceptionUtils.handleThrowable(ex);
+            log.fatal("Exception initializing page context", ex);
+            return null;
+        }
+    }
+
+    private void internalReleasePageContext(PageContext pc) {
+        pc.release();
+        if (USE_POOL && (pc instanceof PageContextImpl)) {
+            localPool.get().put(pc);
+        }
+    }
+
+    private static class PrivilegedGetPageContext
+            implements PrivilegedAction<PageContext> {
+
+        private JspFactoryImpl factory;
+        private Servlet servlet;
+        private ServletRequest request;
+        private ServletResponse response;
+        private String errorPageURL;
+        private boolean needsSession;
+        private int bufferSize;
+        private boolean autoflush;
+
+        PrivilegedGetPageContext(JspFactoryImpl factory, Servlet servlet,
+                ServletRequest request, ServletResponse response, String errorPageURL,
+                boolean needsSession, int bufferSize, boolean autoflush) {
+            this.factory = factory;
+            this.servlet = servlet;
+            this.request = request;
+            this.response = response;
+            this.errorPageURL = errorPageURL;
+            this.needsSession = needsSession;
+            this.bufferSize = bufferSize;
+            this.autoflush = autoflush;
+        }
+
+        @Override
+        public PageContext run() {
+            return factory.internalGetPageContext(servlet, request, response,
+                    errorPageURL, needsSession, bufferSize, autoflush);
+        }
+    }
+
+    private static class PrivilegedReleasePageContext
+            implements PrivilegedAction<Void> {
+
+        private JspFactoryImpl factory;
+        private PageContext pageContext;
+
+        PrivilegedReleasePageContext(JspFactoryImpl factory,
+                PageContext pageContext) {
+            this.factory = factory;
+            this.pageContext = pageContext;
+        }
+
+        @Override
+        public Void run() {
+            factory.internalReleasePageContext(pageContext);
+            return null;
+        }
+    }
+
+    protected static final class PageContextPool  {
+
+        private PageContext[] pool;
+
+        private int current = -1;
+
+        public PageContextPool() {
+            this.pool = new PageContext[POOL_SIZE];
+        }
+
+        public void put(PageContext o) {
+            if (current < (POOL_SIZE - 1)) {
+                current++;
+                pool[current] = o;
+            }
+        }
+
+        public PageContext get() {
+            PageContext item = null;
+            if (current >= 0) {
+                item = pool[current];
+                current--;
+            }
+            return item;
+        }
+
+    }
+
+    @Override
+    public JspApplicationContext getJspApplicationContext(
+            final ServletContext context) {
+        if (Constants.IS_SECURITY_ENABLED) {
+            return AccessController.doPrivileged(
+                    new PrivilegedAction<JspApplicationContext>() {
+                @Override
+                public JspApplicationContext run() {
+                    return JspApplicationContextImpl.getInstance(context);
+                }
+            });
+        } else {
+            return JspApplicationContextImpl.getInstance(context);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspFragmentHelper.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspFragmentHelper.java
new file mode 100644
index 0000000..f0054bb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspFragmentHelper.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.JspFragment;
+import javax.servlet.jsp.tagext.JspTag;
+
+/**
+ * Helper class from which all Jsp Fragment helper classes extend.
+ * This class allows for the emulation of numerous fragments within
+ * a single class, which in turn reduces the load on the class loader
+ * since there are potentially many JspFragments in a single page.
+ * <p>
+ * The class also provides various utility methods for JspFragment
+ * implementations.
+ *
+ * @author Mark Roth
+ */
+public abstract class JspFragmentHelper 
+    extends JspFragment 
+{
+    
+    protected int discriminator;
+    protected JspContext jspContext;
+    protected PageContext _jspx_page_context;
+    protected JspTag parentTag;
+
+    public JspFragmentHelper( int discriminator, JspContext jspContext, 
+        JspTag parentTag ) 
+    {
+        this.discriminator = discriminator;
+        this.jspContext = jspContext;
+        this._jspx_page_context = null;
+        if( jspContext instanceof PageContext ) {
+            _jspx_page_context = (PageContext)jspContext;
+        }
+        this.parentTag = parentTag;
+    }
+    
+    @Override
+    public JspContext getJspContext() {
+        return this.jspContext;
+    }
+    
+    public JspTag getParentTag() {
+        return this.parentTag;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspRuntimeLibrary.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspRuntimeLibrary.java
new file mode 100644
index 0000000..d739fdf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspRuntimeLibrary.java
@@ -0,0 +1,1006 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Enumeration;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.BodyContent;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * Bunch of util methods that are used by code generated for useBean,
+ * getProperty and setProperty.  
+ *
+ * The __begin, __end stuff is there so that the JSP engine can
+ * actually parse this file and inline them if people don't want
+ * runtime dependencies on this class. However, I'm not sure if that
+ * works so well right now. It got forgotten at some point. -akv
+ *
+ * @author Mandar Raje
+ * @author Shawn Bayern
+ */
+public class JspRuntimeLibrary {
+    
+    protected static class PrivilegedIntrospectHelper
+        implements PrivilegedExceptionAction<Void> {
+
+        private Object bean;
+        private String prop;
+        private String value;
+        private ServletRequest request;
+        private String param;
+        private boolean ignoreMethodNF;
+
+        PrivilegedIntrospectHelper(Object bean, String prop,
+                                   String value, ServletRequest request,
+                                   String param, boolean ignoreMethodNF)
+        {
+            this.bean = bean;
+            this.prop = prop;
+            this.value = value;
+            this.request = request;
+            this.param = param;
+            this.ignoreMethodNF = ignoreMethodNF;
+        }
+         
+        @Override
+        public Void run() throws JasperException {
+            internalIntrospecthelper(
+                bean,prop,value,request,param,ignoreMethodNF);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value of the javax.servlet.error.exception request
+     * attribute value, if present, otherwise the value of the
+     * javax.servlet.jsp.jspException request attribute value.
+     *
+     * This method is called at the beginning of the generated servlet code
+     * for a JSP error page, when the "exception" implicit scripting language
+     * variable is initialized.
+     */
+    public static Throwable getThrowable(ServletRequest request) {
+        Throwable error = (Throwable) request.getAttribute(
+                RequestDispatcher.ERROR_EXCEPTION);
+        if (error == null) {
+            error = (Throwable) request.getAttribute(PageContext.EXCEPTION);
+            if (error != null) {
+                /*
+                 * The only place that sets JSP_EXCEPTION is
+                 * PageContextImpl.handlePageException(). It really should set
+                 * SERVLET_EXCEPTION, but that would interfere with the 
+                 * ErrorReportValve. Therefore, if JSP_EXCEPTION is set, we
+                 * need to set SERVLET_EXCEPTION.
+                 */
+                request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, error);
+            }
+        }
+
+        return error;
+    }
+
+    public static boolean coerceToBoolean(String s) {
+        if (s == null || s.length() == 0)
+            return false;
+        else
+            return Boolean.parseBoolean(s);
+    }
+
+    public static byte coerceToByte(String s) {
+        if (s == null || s.length() == 0)
+            return (byte) 0;
+        else
+            return Byte.parseByte(s);
+    }
+
+    public static char coerceToChar(String s) {
+        if (s == null || s.length() == 0) {
+            return (char) 0;
+        } else {
+            // this trick avoids escaping issues
+            return (char)(int) s.charAt(0);
+        }
+    }
+
+    public static double coerceToDouble(String s) {
+        if (s == null || s.length() == 0)
+            return 0;
+        else
+            return Double.parseDouble(s);
+    }
+
+    public static float coerceToFloat(String s) {
+        if (s == null || s.length() == 0)
+            return 0;
+        else
+            return Float.parseFloat(s);
+    }
+
+    public static int coerceToInt(String s) {
+        if (s == null || s.length() == 0)
+            return 0;
+        else
+            return Integer.parseInt(s);
+    }
+
+    public static short coerceToShort(String s) {
+        if (s == null || s.length() == 0)
+            return (short) 0;
+        else
+            return Short.parseShort(s);
+    }
+
+    public static long coerceToLong(String s) {
+        if (s == null || s.length() == 0)
+            return 0;
+        else
+            return Long.parseLong(s);
+    }
+
+    public static Object coerce(String s, Class<?> target) {
+
+        boolean isNullOrEmpty = (s == null || s.length() == 0);
+
+        if (target == Boolean.class) {
+            if (isNullOrEmpty) {
+                s = "false";
+            }
+            return Boolean.valueOf(s);
+        } else if (target == Byte.class) {
+            if (isNullOrEmpty)
+                return Byte.valueOf((byte) 0);
+            else
+                return Byte.valueOf(s);
+        } else if (target == Character.class) {
+            if (isNullOrEmpty)
+                return Character.valueOf((char) 0);
+            else 
+                return Character.valueOf(s.charAt(0));
+        } else if (target == Double.class) {
+            if (isNullOrEmpty)
+                return Double.valueOf(0);
+            else
+                return Double.valueOf(s);
+        } else if (target == Float.class) {
+            if (isNullOrEmpty)
+                return Float.valueOf(0);
+            else
+                return Float.valueOf(s);
+        } else if (target == Integer.class) {
+            if (isNullOrEmpty)
+                return Integer.valueOf(0);
+            else
+                return Integer.valueOf(s);
+        } else if (target == Short.class) {
+            if (isNullOrEmpty)
+                return Short.valueOf((short) 0);
+            else
+                return Short.valueOf(s);
+        } else if (target == Long.class) {
+            if (isNullOrEmpty)
+                return Long.valueOf(0);
+            else
+                return Long.valueOf(s);
+        } else {
+            return null;
+        }
+    }
+
+   // __begin convertMethod
+    public static Object convert(String propertyName, String s, Class<?> t,
+            Class<?> propertyEditorClass) 
+       throws JasperException 
+    {
+        try {
+            if (s == null) {
+                if (t.equals(Boolean.class) || t.equals(Boolean.TYPE))
+                    s = "false";
+                else
+                    return null;
+            }
+            if (propertyEditorClass != null) {
+                return getValueFromBeanInfoPropertyEditor(
+                                    t, propertyName, s, propertyEditorClass);
+            } else if ( t.equals(Boolean.class) || t.equals(Boolean.TYPE) ) {
+                if (s.equalsIgnoreCase("on") || s.equalsIgnoreCase("true"))
+                    s = "true";
+                else
+                    s = "false";
+                return Boolean.valueOf(s);
+            } else if ( t.equals(Byte.class) || t.equals(Byte.TYPE) ) {
+                return Byte.valueOf(s);
+            } else if (t.equals(Character.class) || t.equals(Character.TYPE)) {
+                return s.length() > 0 ? Character.valueOf(s.charAt(0)) : null;
+            } else if ( t.equals(Short.class) || t.equals(Short.TYPE) ) {
+                return Short.valueOf(s);
+            } else if ( t.equals(Integer.class) || t.equals(Integer.TYPE) ) {
+                return Integer.valueOf(s);
+            } else if ( t.equals(Float.class) || t.equals(Float.TYPE) ) {
+                return Float.valueOf(s);
+            } else if ( t.equals(Long.class) || t.equals(Long.TYPE) ) {
+                return Long.valueOf(s);
+            } else if ( t.equals(Double.class) || t.equals(Double.TYPE) ) {
+                return Double.valueOf(s);
+            } else if ( t.equals(String.class) ) {
+                return s;
+            } else if ( t.equals(java.io.File.class) ) {
+                return new java.io.File(s);
+            } else if (t.getName().equals("java.lang.Object")) {
+                return new Object[] {s};
+            } else {
+                return getValueFromPropertyEditorManager(
+                                            t, propertyName, s);
+            }
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }
+    }
+    // __end convertMethod
+
+    // __begin introspectMethod
+    public static void introspect(Object bean, ServletRequest request)
+                                  throws JasperException
+    {
+        Enumeration<String> e = request.getParameterNames();
+        while ( e.hasMoreElements() ) {
+            String name  = e.nextElement();
+            String value = request.getParameter(name);
+            introspecthelper(bean, name, value, request, name, true);
+        }
+    }
+    // __end introspectMethod
+    
+    // __begin introspecthelperMethod
+    public static void introspecthelper(Object bean, String prop,
+                                        String value, ServletRequest request,
+                                        String param, boolean ignoreMethodNF)
+                                        throws JasperException
+    {
+        if( Constants.IS_SECURITY_ENABLED ) {
+            try {
+                PrivilegedIntrospectHelper dp =
+                    new PrivilegedIntrospectHelper(
+                        bean,prop,value,request,param,ignoreMethodNF);
+                AccessController.doPrivileged(dp);
+            } catch( PrivilegedActionException pe) {
+                Exception e = pe.getException();
+                throw (JasperException)e;
+            }
+        } else {
+            internalIntrospecthelper(
+                bean,prop,value,request,param,ignoreMethodNF);
+        }
+    }
+
+    private static void internalIntrospecthelper(Object bean, String prop,
+                                        String value, ServletRequest request,
+                                        String param, boolean ignoreMethodNF) 
+                                        throws JasperException
+    {
+        Method method = null;
+        Class<?> type = null;
+        Class<?> propertyEditorClass = null;
+        try {
+            java.beans.BeanInfo info
+                = java.beans.Introspector.getBeanInfo(bean.getClass());
+            if ( info != null ) {
+                java.beans.PropertyDescriptor pd[]
+                    = info.getPropertyDescriptors();
+                for (int i = 0 ; i < pd.length ; i++) {
+                    if ( pd[i].getName().equals(prop) ) {
+                        method = pd[i].getWriteMethod();
+                        type   = pd[i].getPropertyType();
+                        propertyEditorClass = pd[i].getPropertyEditorClass();
+                        break;
+                    }
+                }
+            }
+            if ( method != null ) {
+                if (type.isArray()) {
+                    if (request == null) {
+                        throw new JasperException(
+                            Localizer.getMessage("jsp.error.beans.setproperty.noindexset"));
+                    }
+                    Class<?> t = type.getComponentType();
+                    String[] values = request.getParameterValues(param);
+                    //XXX Please check.
+                    if(values == null) return;
+                    if(t.equals(String.class)) {
+                        method.invoke(bean, new Object[] { values });
+                    } else {
+                        createTypedArray (prop, bean, method, values, t,
+                                          propertyEditorClass); 
+                    }
+                } else {
+                    if(value == null || (param != null && value.equals(""))) return;
+                    Object oval = convert(prop, value, type, propertyEditorClass);
+                    if ( oval != null )
+                        method.invoke(bean, new Object[] { oval });
+                }
+            }
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }
+        if (!ignoreMethodNF && (method == null)) {
+            if (type == null) {
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.noproperty",
+                                         prop,
+                                         bean.getClass().getName()));
+            } else {
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nomethod.setproperty",
+                                         prop,
+                                         type.getName(),
+                                         bean.getClass().getName()));
+            }
+        }
+    }
+    // __end introspecthelperMethod
+    
+    //-------------------------------------------------------------------
+    // functions to convert builtin Java data types to string.
+    //-------------------------------------------------------------------
+    // __begin toStringMethod
+    public static String toString(Object o) {
+        return String.valueOf(o);
+    }
+
+    public static String toString(byte b) {
+        return Byte.toString(b);
+    }
+
+    public static String toString(boolean b) {
+        return Boolean.toString(b);
+    }
+
+    public static String toString(short s) {
+        return Short.toString(s);
+    }
+
+    public static String toString(int i) {
+        return Integer.toString(i);
+    }
+
+    public static String toString(float f) {
+        return Float.toString(f);
+    }
+
+    public static String toString(long l) {
+        return Long.toString(l);
+    }
+
+    public static String toString(double d) {
+        return Double.toString(d);
+    }
+
+    public static String toString(char c) {
+        return Character.toString(c);
+    }
+    // __end toStringMethod
+
+
+    /**
+     * Create a typed array.
+     * This is a special case where params are passed through
+     * the request and the property is indexed.
+     */
+    public static void createTypedArray(String propertyName,
+                                        Object bean,
+                                        Method method,
+                                        String[] values,
+                                        Class<?> t,
+                                        Class<?> propertyEditorClass)
+                throws JasperException {
+
+        try {
+            if (propertyEditorClass != null) {
+                Object[] tmpval = new Integer[values.length];
+                for (int i=0; i<values.length; i++) {
+                    tmpval[i] = getValueFromBeanInfoPropertyEditor(
+                            t, propertyName, values[i], propertyEditorClass);
+                }
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Integer.class)) {
+                Integer []tmpval = new Integer[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] =  new Integer (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Byte.class)) {
+                Byte[] tmpval = new Byte[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = new Byte (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Boolean.class)) {
+                Boolean[] tmpval = new Boolean[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Boolean.valueOf(values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Short.class)) {
+                Short[] tmpval = new Short[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = new Short (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Long.class)) {
+                Long[] tmpval = new Long[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = new Long (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Double.class)) {
+                Double[] tmpval = new Double[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = new Double (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Float.class)) {
+                Float[] tmpval = new Float[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = new Float (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(Character.class)) {
+                Character[] tmpval = new Character[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Character.valueOf(values[i].charAt(0));
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(int.class)) {
+                int []tmpval = new int[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Integer.parseInt (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(byte.class)) {
+                byte[] tmpval = new byte[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Byte.parseByte (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(boolean.class)) {
+                boolean[] tmpval = new boolean[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = (Boolean.valueOf(values[i])).booleanValue();
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(short.class)) {
+                short[] tmpval = new short[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Short.parseShort (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(long.class)) {
+                long[] tmpval = new long[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Long.parseLong (values[i]);
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(double.class)) {
+                double[] tmpval = new double[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Double.valueOf(values[i]).doubleValue();
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(float.class)) {
+                float[] tmpval = new float[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = Float.valueOf(values[i]).floatValue();
+                method.invoke (bean, new Object[] {tmpval});
+            } else if (t.equals(char.class)) {
+                char[] tmpval = new char[values.length];
+                for (int i = 0 ; i < values.length; i++)
+                    tmpval[i] = values[i].charAt(0);
+                method.invoke (bean, new Object[] {tmpval});
+            } else {
+                Object[] tmpval = new Integer[values.length];
+                for (int i=0; i<values.length; i++) {
+                    tmpval[i] =  
+                        getValueFromPropertyEditorManager(
+                                            t, propertyName, values[i]);
+                }
+                method.invoke (bean, new Object[] {tmpval});
+            }
+        } catch (Exception ex) {
+            throw new JasperException ("error in invoking method", ex);
+        }
+    }
+
+    /**
+     * Escape special shell characters.
+     * @param unescString The string to shell-escape
+     * @return The escaped shell string.
+     */
+
+    public static String escapeQueryString(String unescString) {
+    if ( unescString == null )
+        return null;
+   
+    String escString    = "";
+    String shellSpChars = "&;`'\"|*?~<>^()[]{}$\\\n";
+   
+    for(int index=0; index<unescString.length(); index++) {
+        char nextChar = unescString.charAt(index);
+
+        if( shellSpChars.indexOf(nextChar) != -1 )
+        escString += "\\";
+
+        escString += nextChar;
+    }
+    return escString;
+    }
+
+    // __begin lookupReadMethodMethod
+    public static Object handleGetProperty(Object o, String prop)
+    throws JasperException {
+        if (o == null) {
+            throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nullbean"));
+        }
+        Object value = null;
+        try {
+            Method method = getReadMethod(o.getClass(), prop);
+            value = method.invoke(o, (Object[]) null);
+        } catch (Exception ex) {
+            throw new JasperException (ex);
+        }
+        return value;
+    }
+    // __end lookupReadMethodMethod
+
+    // handles <jsp:setProperty> with EL expression for 'value' attribute
+/** Use proprietaryEvaluate
+    public static void handleSetPropertyExpression(Object bean,
+        String prop, String expression, PageContext pageContext,
+        VariableResolver variableResolver, FunctionMapper functionMapper )
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { 
+                pageContext.getExpressionEvaluator().evaluate(
+                    expression,
+                    method.getParameterTypes()[0],
+                    variableResolver,
+                    functionMapper,
+                    null )
+            });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }
+    }
+**/
+    public static void handleSetPropertyExpression(Object bean,
+        String prop, String expression, PageContext pageContext,
+        ProtectedFunctionMapper functionMapper )
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] {
+                PageContextImpl.proprietaryEvaluate(
+                    expression,
+                    method.getParameterTypes()[0],
+                    pageContext,
+                    functionMapper,
+                    false )
+            });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }
+    }
+
+    public static void handleSetProperty(Object bean, String prop,
+                                         Object value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { value });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+                                         int value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Integer.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+                                         short value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Short.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+                                         long value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Long.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    } 
+    
+    public static void handleSetProperty(Object bean, String prop,
+                                         double value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Double.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+                                         float value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Float.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+                                         char value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Character.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    }
+
+    public static void handleSetProperty(Object bean, String prop,
+                                         byte value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Byte.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    }
+    
+    public static void handleSetProperty(Object bean, String prop,
+                                         boolean value)
+        throws JasperException
+    {
+        try {
+            Method method = getWriteMethod(bean.getClass(), prop);
+            method.invoke(bean, new Object[] { Boolean.valueOf(value) });
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        }        
+    }
+    
+    public static Method getWriteMethod(Class<?> beanClass, String prop)
+    throws JasperException {
+        Method method = null;        
+        Class<?> type = null;
+        try {
+            java.beans.BeanInfo info
+                = java.beans.Introspector.getBeanInfo(beanClass);
+            if ( info != null ) {
+                java.beans.PropertyDescriptor pd[]
+                    = info.getPropertyDescriptors();
+                for (int i = 0 ; i < pd.length ; i++) {
+                    if ( pd[i].getName().equals(prop) ) {
+                        method = pd[i].getWriteMethod();
+                        type   = pd[i].getPropertyType();
+                        break;
+                    }
+                }
+            } else {        
+                // just in case introspection silently fails.
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nobeaninfo",
+                                         beanClass.getName()));
+            }
+        } catch (Exception ex) {
+            throw new JasperException (ex);
+        }
+        if (method == null) {
+            if (type == null) {
+                throw new JasperException(
+                        Localizer.getMessage("jsp.error.beans.noproperty",
+                                             prop,
+                                             beanClass.getName()));
+            } else {
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nomethod.setproperty",
+                                         prop,
+                                         type.getName(),
+                                         beanClass.getName()));
+            }
+        }
+        return method;
+    }
+
+    public static Method getReadMethod(Class<?> beanClass, String prop)
+            throws JasperException {
+
+        Method method = null;        
+        Class<?> type = null;
+        try {
+            java.beans.BeanInfo info
+                = java.beans.Introspector.getBeanInfo(beanClass);
+            if ( info != null ) {
+                java.beans.PropertyDescriptor pd[]
+                    = info.getPropertyDescriptors();
+                for (int i = 0 ; i < pd.length ; i++) {
+                    if ( pd[i].getName().equals(prop) ) {
+                        method = pd[i].getReadMethod();
+                        type   = pd[i].getPropertyType();
+                        break;
+                    }
+                }
+            } else {        
+                // just in case introspection silently fails.
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nobeaninfo",
+                                         beanClass.getName()));
+            }
+        } catch (Exception ex) {
+            throw new JasperException (ex);
+        }
+        if (method == null) {
+            if (type == null) {
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.noproperty", prop,
+                                         beanClass.getName()));
+            } else {
+                throw new JasperException(
+                    Localizer.getMessage("jsp.error.beans.nomethod", prop,
+                                         beanClass.getName()));
+            }
+        }
+
+        return method;
+    }
+
+    //*********************************************************************
+    // PropertyEditor Support
+
+    public static Object getValueFromBeanInfoPropertyEditor(
+                           Class<?> attrClass, String attrName, String attrValue,
+                           Class<?> propertyEditorClass) 
+        throws JasperException 
+    {
+        try {
+            PropertyEditor pe =
+                (PropertyEditor)propertyEditorClass.newInstance();
+            pe.setAsText(attrValue);
+            return pe.getValue();
+        } catch (Exception ex) {
+            throw new JasperException(
+                Localizer.getMessage("jsp.error.beans.property.conversion",
+                                     attrValue, attrClass.getName(), attrName,
+                                     ex.getMessage()));
+        }
+    }
+
+    public static Object getValueFromPropertyEditorManager(
+                     Class<?> attrClass, String attrName, String attrValue) 
+        throws JasperException 
+    {
+        try {
+            PropertyEditor propEditor = 
+                PropertyEditorManager.findEditor(attrClass);
+            if (propEditor != null) {
+                propEditor.setAsText(attrValue);
+                return propEditor.getValue();
+            } else {
+                throw new IllegalArgumentException(
+                    Localizer.getMessage("jsp.error.beans.propertyeditor.notregistered"));
+            }
+        } catch (IllegalArgumentException ex) {
+            throw new JasperException(
+                Localizer.getMessage("jsp.error.beans.property.conversion",
+                                     attrValue, attrClass.getName(), attrName,
+                                     ex.getMessage()));
+        }
+    }
+
+
+    // ************************************************************************
+    // General Purpose Runtime Methods
+    // ************************************************************************
+
+
+    /**
+     * Convert a possibly relative resource path into a context-relative
+     * resource path that starts with a '/'.
+     *
+     * @param request The servlet request we are processing
+     * @param relativePath The possibly relative resource path
+     */
+    public static String getContextRelativePath(ServletRequest request,
+                                                String relativePath) {
+
+        if (relativePath.startsWith("/"))
+            return (relativePath);
+        if (!(request instanceof HttpServletRequest))
+            return (relativePath);
+        HttpServletRequest hrequest = (HttpServletRequest) request;
+        String uri = (String) request.getAttribute(
+                RequestDispatcher.INCLUDE_SERVLET_PATH);
+        if (uri != null) {
+            String pathInfo = (String)
+                request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+            if (pathInfo == null) {
+                if (uri.lastIndexOf('/') >= 0) 
+                    uri = uri.substring(0, uri.lastIndexOf('/'));
+            }
+        }
+        else {
+            uri = hrequest.getServletPath();
+            if (uri.lastIndexOf('/') >= 0) 
+                uri = uri.substring(0, uri.lastIndexOf('/'));
+        }
+        return uri + '/' + relativePath;
+
+    }
+
+
+    /**
+     * Perform a RequestDispatcher.include() operation, with optional flushing
+     * of the response beforehand.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are processing
+     * @param relativePath The relative path of the resource to be included
+     * @param out The Writer to whom we are currently writing
+     * @param flush Should we flush before the include is processed?
+     *
+     * @exception IOException if thrown by the included servlet
+     * @exception ServletException if thrown by the included servlet
+     */
+    public static void include(ServletRequest request,
+                               ServletResponse response,
+                               String relativePath,
+                               JspWriter out,
+                               boolean flush)
+        throws IOException, ServletException {
+
+        if (flush && !(out instanceof BodyContent))
+            out.flush();
+
+        // FIXME - It is tempting to use request.getRequestDispatcher() to
+        // resolve a relative path directly, but Catalina currently does not
+        // take into account whether the caller is inside a RequestDispatcher
+        // include or not.  Whether Catalina *should* take that into account
+        // is a spec issue currently under review.  In the mean time,
+        // replicate Jasper's previous behavior
+
+        String resourcePath = getContextRelativePath(request, relativePath);
+        RequestDispatcher rd = request.getRequestDispatcher(resourcePath);
+
+        rd.include(request,
+                   new ServletResponseWrapperInclude(response, out));
+
+    }
+
+    /**
+     * URL encodes a string, based on the supplied character encoding.
+     * This performs the same function as java.next.URLEncode.encode
+     * in J2SDK1.4, and should be removed if the only platform supported
+     * is 1.4 or higher.
+     * @param s The String to be URL encoded.
+     * @param enc The character encoding 
+     * @return The URL encoded String
+     */
+    public static String URLEncode(String s, String enc) {
+
+        if (s == null) {
+            return "null";
+        }
+
+        if (enc == null) {
+            enc = "ISO-8859-1";        // The default request encoding 
+        }
+
+        StringBuilder out = new StringBuilder(s.length());
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+        OutputStreamWriter writer = null;
+        try {
+            writer = new OutputStreamWriter(buf, enc);
+        } catch (java.io.UnsupportedEncodingException ex) {
+            // Use the default encoding?
+            writer = new OutputStreamWriter(buf);
+        }
+        
+        for (int i = 0; i < s.length(); i++) {
+            int c = s.charAt(i);
+            if (c == ' ') {
+                out.append('+');
+            } else if (isSafeChar(c)) {
+                out.append((char)c);
+            } else {
+                // convert to external encoding before hex conversion
+                try {
+                    writer.write(c);
+                    writer.flush();
+                } catch(IOException e) {
+                    buf.reset();
+                    continue;
+                }
+                byte[] ba = buf.toByteArray();
+                for (int j = 0; j < ba.length; j++) {
+                    out.append('%');
+                    // Converting each byte in the buffer
+                    out.append(Character.forDigit((ba[j]>>4) & 0xf, 16));
+                    out.append(Character.forDigit(ba[j] & 0xf, 16));
+                }
+                buf.reset();
+            }
+        }
+        return out.toString();
+    }
+
+    private static boolean isSafeChar(int c) {
+        if (c >= 'a' && c <= 'z') {
+            return true;
+        }
+        if (c >= 'A' && c <= 'Z') {
+            return true;
+        }
+        if (c >= '0' && c <= '9') {
+            return true;
+        }
+        if (c == '-' || c == '_' || c == '.' || c == '!' ||
+            c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') {
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspSourceDependent.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspSourceDependent.java
new file mode 100644
index 0000000..7217a3f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspSourceDependent.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.util.List;
+
+/**
+ * Interface for tracking the source files dependencies, for the purpose
+ * of compiling out of date pages.  This is used for
+ * 1) files that are included by page directives
+ * 2) files that are included by include-prelude and include-coda in jsp:config
+ * 3) files that are tag files and referenced
+ * 4) TLDs referenced
+ */
+
+public interface JspSourceDependent {
+
+   /**
+    * Returns a list of files names that the current page has a source
+    * dependency on.
+    */
+    public List<String> getDependants();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspWriterImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspWriterImpl.java
new file mode 100644
index 0000000..92a07a9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/JspWriterImpl.java
@@ -0,0 +1,621 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.jsp.JspWriter;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.security.SecurityUtil;
+
+/**
+ * Write text to a character-output stream, buffering characters so as
+ * to provide for the efficient writing of single characters, arrays,
+ * and strings. 
+ *
+ * Provide support for discarding for the output that has been 
+ * buffered. 
+ * 
+ * This needs revisiting when the buffering problems in the JSP spec
+ * are fixed -akv 
+ *
+ * @author Anil K. Vijendran
+ */
+public class JspWriterImpl extends JspWriter {
+    
+    private Writer out;
+    private ServletResponse response;    
+    private char cb[];
+    private int nextChar;
+    private boolean flushed = false;
+    private boolean closed = false;
+    
+    public JspWriterImpl() {
+        super( Constants.DEFAULT_BUFFER_SIZE, true );
+    }
+    
+    /**
+     * Create a buffered character-output stream that uses a default-sized
+     * output buffer.
+     *
+     * @param  response  A Servlet Response
+     */
+    public JspWriterImpl(ServletResponse response) {
+        this(response, Constants.DEFAULT_BUFFER_SIZE, true);
+    }
+    
+    /**
+     * Create a new buffered character-output stream that uses an output
+     * buffer of the given size.
+     *
+     * @param  response A Servlet Response
+     * @param  sz       Output-buffer size, a positive integer
+     *
+     * @exception  IllegalArgumentException  If sz is <= 0
+     */
+    public JspWriterImpl(ServletResponse response, int sz, 
+            boolean autoFlush) {
+        super(sz, autoFlush);
+        if (sz < 0)
+            throw new IllegalArgumentException("Buffer size <= 0");
+        this.response = response;
+        cb = sz == 0 ? null : new char[sz];
+        nextChar = 0;
+    }
+    
+    void init( ServletResponse response, int sz, boolean autoFlush ) {
+        this.response= response;
+        if( sz > 0 && ( cb == null || sz > cb.length ) )
+            cb=new char[sz];
+        nextChar = 0;
+        this.autoFlush=autoFlush;
+        this.bufferSize=sz;
+    }
+    
+    /** Package-level access
+     */
+    void recycle() {
+        flushed = false;
+        closed = false;
+        out = null;
+        nextChar = 0;
+        response = null;
+    }
+    
+    /**
+     * Flush the output buffer to the underlying character stream, without
+     * flushing the stream itself.  This method is non-private only so that it
+     * may be invoked by PrintStream.
+     */
+    protected final void flushBuffer() throws IOException {
+        if (bufferSize == 0)
+            return;
+        flushed = true;
+        ensureOpen();
+        if (nextChar == 0)
+            return;
+        initOut();
+        out.write(cb, 0, nextChar);
+        nextChar = 0;
+    }
+    
+    private void initOut() throws IOException {
+        if (out == null) {
+            out = response.getWriter();
+        }
+    }
+    
+    private String getLocalizeMessage(final String message){
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            return AccessController.doPrivileged(new PrivilegedAction<String>(){
+                @Override
+                public String run(){
+                    return Localizer.getMessage(message); 
+                }
+            });
+        } else {
+            return Localizer.getMessage(message);
+        }
+    }
+    
+    /**
+     * Discard the output buffer.
+     */
+    @Override
+    public final void clear() throws IOException {
+        if ((bufferSize == 0) && (out != null))
+            // clear() is illegal after any unbuffered output (JSP.5.5)
+            throw new IllegalStateException(
+                    getLocalizeMessage("jsp.error.ise_on_clear"));
+        if (flushed)
+            throw new IOException(
+                    getLocalizeMessage("jsp.error.attempt_to_clear_flushed_buffer"));
+        ensureOpen();
+        nextChar = 0;
+    }
+    
+    @Override
+    public void clearBuffer() throws IOException {
+        if (bufferSize == 0)
+            throw new IllegalStateException(
+                    getLocalizeMessage("jsp.error.ise_on_clear"));
+        ensureOpen();
+        nextChar = 0;
+    }
+    
+    private final void bufferOverflow() throws IOException {
+        throw new IOException(getLocalizeMessage("jsp.error.overflow"));
+    }
+    
+    /**
+     * Flush the stream.
+     *
+     */
+    @Override
+    public void flush()  throws IOException {
+        flushBuffer();
+        if (out != null) {
+            out.flush();
+        }
+    }
+    
+    /**
+     * Close the stream.
+     *
+     */
+    @Override
+    public void close() throws IOException {
+        if (response == null || closed)
+            // multiple calls to close is OK
+            return;
+        flush();
+        if (out != null)
+            out.close();
+        out = null;
+        closed = true;
+    }
+    
+    /**
+     * @return the number of bytes unused in the buffer
+     */
+    @Override
+    public int getRemaining() {
+        return bufferSize - nextChar;
+    }
+    
+    /** check to make sure that the stream has not been closed */
+    private void ensureOpen() throws IOException {
+        if (response == null || closed)
+            throw new IOException("Stream closed");
+    }
+    
+    
+    /**
+     * Write a single character.
+     */
+    @Override
+    public void write(int c) throws IOException {
+        ensureOpen();
+        if (bufferSize == 0) {
+            initOut();
+            out.write(c);
+        }
+        else {
+            if (nextChar >= bufferSize)
+                if (autoFlush)
+                    flushBuffer();
+                else
+                    bufferOverflow();
+            cb[nextChar++] = (char) c;
+        }
+    }
+    
+    /**
+     * Our own little min method, to avoid loading java.lang.Math if we've run
+     * out of file descriptors and we're trying to print a stack trace.
+     */
+    private static int min(int a, int b) {
+        if (a < b) return a;
+        return b;
+    }
+    
+    /**
+     * Write a portion of an array of characters.
+     *
+     * <p> Ordinarily this method stores characters from the given array into
+     * this stream's buffer, flushing the buffer to the underlying stream as
+     * needed.  If the requested length is at least as large as the buffer,
+     * however, then this method will flush the buffer and write the characters
+     * directly to the underlying stream.  Thus redundant
+     * <code>DiscardableBufferedWriter</code>s will not copy data unnecessarily.
+     *
+     * @param  cbuf  A character array
+     * @param  off   Offset from which to start reading characters
+     * @param  len   Number of characters to write
+     */
+    @Override
+    public void write(char cbuf[], int off, int len) 
+    throws IOException 
+    {
+        ensureOpen();
+        
+        if (bufferSize == 0) {
+            initOut();
+            out.write(cbuf, off, len);
+            return;
+        }
+        
+        if ((off < 0) || (off > cbuf.length) || (len < 0) ||
+                ((off + len) > cbuf.length) || ((off + len) < 0)) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return;
+        } 
+        
+        if (len >= bufferSize) {
+            /* If the request length exceeds the size of the output buffer,
+             flush the buffer and then write the data directly.  In this
+             way buffered streams will cascade harmlessly. */
+            if (autoFlush)
+                flushBuffer();
+            else
+                bufferOverflow();
+            initOut();
+            out.write(cbuf, off, len);
+            return;
+        }
+        
+        int b = off, t = off + len;
+        while (b < t) {
+            int d = min(bufferSize - nextChar, t - b);
+            System.arraycopy(cbuf, b, cb, nextChar, d);
+            b += d;
+            nextChar += d;
+            if (nextChar >= bufferSize) 
+                if (autoFlush)
+                    flushBuffer();
+                else
+                    bufferOverflow();
+        }
+        
+    }
+    
+    /**
+     * Write an array of characters.  This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     */
+    @Override
+    public void write(char buf[]) throws IOException {
+        write(buf, 0, buf.length);
+    }
+    
+    /**
+     * Write a portion of a String.
+     *
+     * @param  s     String to be written
+     * @param  off   Offset from which to start reading characters
+     * @param  len   Number of characters to be written
+     */
+    @Override
+    public void write(String s, int off, int len) throws IOException {
+        ensureOpen();
+        if (bufferSize == 0) {
+            initOut();
+            out.write(s, off, len);
+            return;
+        }
+        int b = off, t = off + len;
+        while (b < t) {
+            int d = min(bufferSize - nextChar, t - b);
+            s.getChars(b, b + d, cb, nextChar);
+            b += d;
+            nextChar += d;
+            if (nextChar >= bufferSize) 
+                if (autoFlush)
+                    flushBuffer();
+                else
+                    bufferOverflow();
+        }
+    }
+    
+    /**
+     * Write a string.  This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     */
+    @Override
+    public void write(String s) throws IOException {
+        // Simple fix for Bugzilla 35410
+        // Calling the other write function so as to init the buffer anyways
+        if(s == null) {
+            write(s, 0, 0);
+        } else {
+            write(s, 0, s.length());
+        }
+    }
+    
+    
+    static String lineSeparator = System.getProperty("line.separator");
+    
+    /**
+     * Write a line separator.  The line separator string is defined by the
+     * system property <tt>line.separator</tt>, and is not necessarily a single
+     * newline ('\n') character.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    
+    @Override
+    public void newLine() throws IOException {
+        write(lineSeparator);
+    }
+    
+    
+    /* Methods that do not terminate lines */
+    
+    /**
+     * 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) throws IOException {
+        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) throws IOException {
+        write(String.valueOf(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
+     */
+    @Override
+    public void print(int i) throws IOException {
+        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
+     */
+    @Override
+    public void print(long l) throws IOException {
+        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
+     */
+    @Override
+    public void print(float f) throws IOException {
+        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
+     */
+    @Override
+    public void print(double d) throws IOException {
+        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[]) throws IOException {
+        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) throws IOException {
+        if (s == null) {
+            s = "null";
+        }
+        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
+     */
+    @Override
+    public void print(Object obj) throws IOException {
+        write(String.valueOf(obj));
+    }
+    
+    /* Methods that do terminate lines */
+    
+    /**
+     * 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>).
+     *
+     * Need to change this from PrintWriter because the default
+     * println() writes  to the sink directly instead of through the
+     * write method...  
+     */
+    @Override
+    public void println() throws IOException {
+        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>.
+     */
+    @Override
+    public void println(boolean x) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(char x) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(int x) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(long x) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(float x) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(double x) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(char x[]) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(String x) throws IOException {
+        print(x);
+        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>.
+     */
+    @Override
+    public void println(Object x) throws IOException {
+        print(x);
+        println();
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/PageContextImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/PageContextImpl.java
new file mode 100644
index 0000000..64d385d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/PageContextImpl.java
@@ -0,0 +1,1002 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import javax.el.ELContext;
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+import javax.servlet.RequestDispatcher;
+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.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspFactory;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.VariableResolver;
+import javax.servlet.jsp.tagext.BodyContent;
+
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.el.ELContextImpl;
+import org.apache.jasper.el.ExpressionEvaluatorImpl;
+import org.apache.jasper.el.FunctionMapperImpl;
+import org.apache.jasper.el.VariableResolverImpl;
+import org.apache.jasper.security.SecurityUtil;
+import org.apache.jasper.util.Enumerator;
+
+/**
+ * Implementation of the PageContext class from the JSP spec. Also doubles as a
+ * VariableResolver for the EL.
+ * 
+ * @author Anil K. Vijendran
+ * @author Larry Cable
+ * @author Hans Bergsten
+ * @author Pierre Delisle
+ * @author Mark Roth
+ * @author Jan Luehe
+ * @author Jacob Hookom
+ */
+public class PageContextImpl extends PageContext {
+
+    private static final JspFactory jspf = JspFactory.getDefaultFactory(); 
+
+    private BodyContentImpl[] outs;
+
+    private int depth;
+
+    // per-servlet state
+    private Servlet servlet;
+
+    private ServletConfig config;
+
+    private ServletContext context;
+
+    private JspApplicationContextImpl applicationContext;
+
+    private String errorPageURL;
+
+    // page-scope attributes
+    private transient HashMap<String, Object> attributes;
+
+    // per-request state
+    private transient ServletRequest request;
+
+    private transient ServletResponse response;
+
+    private transient HttpSession session;
+    
+    private transient ELContextImpl elContext;
+
+    private boolean isIncluded;
+    
+    
+    // initial output stream
+    private transient JspWriter out;
+
+    private transient JspWriterImpl baseOut;
+
+    /*
+     * Constructor.
+     */
+    PageContextImpl() {
+        this.outs = new BodyContentImpl[0];
+        this.attributes = new HashMap<String, Object>(16);
+        this.depth = -1;
+    }
+
+    @Override
+    public void initialize(Servlet servlet, ServletRequest request,
+            ServletResponse response, String errorPageURL,
+            boolean needsSession, int bufferSize, boolean autoFlush)
+            throws IOException {
+
+        _initialize(servlet, request, response, errorPageURL, needsSession,
+                bufferSize, autoFlush);
+    }
+
+    private void _initialize(Servlet servlet, ServletRequest request,
+            ServletResponse response, String errorPageURL,
+            boolean needsSession, int bufferSize, boolean autoFlush) {
+
+        // initialize state
+        this.servlet = servlet;
+        this.config = servlet.getServletConfig();
+        this.context = config.getServletContext();
+        this.errorPageURL = errorPageURL;
+        this.request = request;
+        this.response = response;
+        
+        // initialize application context
+        this.applicationContext = JspApplicationContextImpl.getInstance(context);
+
+        // Setup session (if required)
+        if (request instanceof HttpServletRequest && needsSession)
+            this.session = ((HttpServletRequest) request).getSession();
+        if (needsSession && session == null)
+            throw new IllegalStateException(
+                    "Page needs a session and none is available");
+
+        // initialize the initial out ...
+        depth = -1;
+        if (this.baseOut == null) {
+            this.baseOut = new JspWriterImpl(response, bufferSize, autoFlush);
+        } else {
+            this.baseOut.init(response, bufferSize, autoFlush);
+        }
+        this.out = baseOut;
+
+        // register names/values as per spec
+        setAttribute(OUT, this.out);
+        setAttribute(REQUEST, request);
+        setAttribute(RESPONSE, response);
+
+        if (session != null)
+            setAttribute(SESSION, session);
+
+        setAttribute(PAGE, servlet);
+        setAttribute(CONFIG, config);
+        setAttribute(PAGECONTEXT, this);
+        setAttribute(APPLICATION, context);
+
+        isIncluded = request.getAttribute(
+                RequestDispatcher.INCLUDE_SERVLET_PATH) != null;
+    }
+
+    @Override
+    public void release() {
+        out = baseOut;
+        try {
+            if (isIncluded) {
+                ((JspWriterImpl) out).flushBuffer();
+                // push it into the including jspWriter
+            } else {
+                // Old code:
+                // out.flush();
+                // Do not flush the buffer even if we're not included (i.e.
+                // we are the main page. The servlet will flush it and close
+                // the stream.
+                ((JspWriterImpl) out).flushBuffer();
+            }
+        } catch (IOException ex) {
+            IllegalStateException ise = new IllegalStateException(Localizer.getMessage("jsp.error.flush"), ex);
+            throw ise;
+        } finally {
+            servlet = null;
+            config = null;
+            context = null;
+            applicationContext = null;
+            elContext = null;
+            errorPageURL = null;
+            request = null;
+            response = null;
+            depth = -1;
+            baseOut.recycle();
+            session = null;
+            attributes.clear();
+            for (BodyContentImpl body: outs) {
+                body.recycle();
+            }
+        }
+    }
+
+    @Override
+    public Object getAttribute(final String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return AccessController.doPrivileged(
+                    new PrivilegedAction<Object>() {
+                @Override
+                public Object run() {
+                    return doGetAttribute(name);
+                }
+            });
+        } else {
+            return doGetAttribute(name);
+        }
+
+    }
+
+    private Object doGetAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    @Override
+    public Object getAttribute(final String name, final int scope) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return AccessController.doPrivileged(
+                    new PrivilegedAction<Object>() {
+                @Override
+                public Object run() {
+                    return doGetAttribute(name, scope);
+                }
+            });
+        } else {
+            return doGetAttribute(name, scope);
+        }
+
+    }
+
+    private Object doGetAttribute(String name, int scope) {
+        switch (scope) {
+        case PAGE_SCOPE:
+            return attributes.get(name);
+
+        case REQUEST_SCOPE:
+            return request.getAttribute(name);
+
+        case SESSION_SCOPE:
+            if (session == null) {
+                throw new IllegalStateException(Localizer
+                        .getMessage("jsp.error.page.noSession"));
+            }
+            return session.getAttribute(name);
+
+        case APPLICATION_SCOPE:
+            return context.getAttribute(name);
+
+        default:
+            throw new IllegalArgumentException("Invalid scope");
+        }
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object attribute) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    doSetAttribute(name, attribute);
+                    return null;
+                }
+            });
+        } else {
+            doSetAttribute(name, attribute);
+        }
+    }
+
+    private void doSetAttribute(String name, Object attribute) {
+        if (attribute != null) {
+            attributes.put(name, attribute);
+        } else {
+            removeAttribute(name, PAGE_SCOPE);
+        }
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object o, final int scope) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    doSetAttribute(name, o, scope);
+                    return null;
+                }
+            });
+        } else {
+            doSetAttribute(name, o, scope);
+        }
+
+    }
+
+    private void doSetAttribute(String name, Object o, int scope) {
+        if (o != null) {
+            switch (scope) {
+            case PAGE_SCOPE:
+                attributes.put(name, o);
+                break;
+
+            case REQUEST_SCOPE:
+                request.setAttribute(name, o);
+                break;
+
+            case SESSION_SCOPE:
+                if (session == null) {
+                    throw new IllegalStateException(Localizer
+                            .getMessage("jsp.error.page.noSession"));
+                }
+                session.setAttribute(name, o);
+                break;
+
+            case APPLICATION_SCOPE:
+                context.setAttribute(name, o);
+                break;
+
+            default:
+                throw new IllegalArgumentException("Invalid scope");
+            }
+        } else {
+            removeAttribute(name, scope);
+        }
+    }
+
+    @Override
+    public void removeAttribute(final String name, final int scope) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    doRemoveAttribute(name, scope);
+                    return null;
+                }
+            });
+        } else {
+            doRemoveAttribute(name, scope);
+        }
+    }
+
+    private void doRemoveAttribute(String name, int scope) {
+        switch (scope) {
+        case PAGE_SCOPE:
+            attributes.remove(name);
+            break;
+
+        case REQUEST_SCOPE:
+            request.removeAttribute(name);
+            break;
+
+        case SESSION_SCOPE:
+            if (session == null) {
+                throw new IllegalStateException(Localizer
+                        .getMessage("jsp.error.page.noSession"));
+            }
+            session.removeAttribute(name);
+            break;
+
+        case APPLICATION_SCOPE:
+            context.removeAttribute(name);
+            break;
+
+        default:
+            throw new IllegalArgumentException("Invalid scope");
+        }
+    }
+
+    @Override
+    public int getAttributesScope(final String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (AccessController
+                    .doPrivileged(new PrivilegedAction<Integer>() {
+                        @Override
+                        public Integer run() {
+                            return Integer.valueOf(doGetAttributeScope(name));
+                        }
+                    })).intValue();
+        } else {
+            return doGetAttributeScope(name);
+        }
+    }
+
+    private int doGetAttributeScope(String name) {
+        if (attributes.get(name) != null)
+            return PAGE_SCOPE;
+
+        if (request.getAttribute(name) != null)
+            return REQUEST_SCOPE;
+
+        if (session != null) {
+            try {
+                if (session.getAttribute(name) != null)
+                    return SESSION_SCOPE;
+            } catch(IllegalStateException ise) {
+                // Session has been invalidated.
+                // Ignore and fall through to application scope.
+            }
+        }
+
+        if (context.getAttribute(name) != null)
+            return APPLICATION_SCOPE;
+
+        return 0;
+    }
+
+    @Override
+    public Object findAttribute(final String name) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return AccessController.doPrivileged(
+                    new PrivilegedAction<Object>() {
+                @Override
+                public Object run() {
+                    if (name == null) {
+                        throw new NullPointerException(Localizer
+                                .getMessage("jsp.error.attribute.null_name"));
+                    }
+
+                    return doFindAttribute(name);
+                }
+            });
+        } else {
+            if (name == null) {
+                throw new NullPointerException(Localizer
+                        .getMessage("jsp.error.attribute.null_name"));
+            }
+
+            return doFindAttribute(name);
+        }
+    }
+
+    private Object doFindAttribute(String name) {
+
+        Object o = attributes.get(name);
+        if (o != null)
+            return o;
+
+        o = request.getAttribute(name);
+        if (o != null)
+            return o;
+
+        if (session != null) {
+            try {
+                o = session.getAttribute(name);
+            } catch(IllegalStateException ise) {
+                // Session has been invalidated.
+                // Ignore and fall through to application scope.
+            }
+            if (o != null)
+                return o;
+        }
+
+        return context.getAttribute(name);
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNamesInScope(final int scope) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return AccessController.doPrivileged(
+                    new PrivilegedAction<Enumeration<String>>() {
+                        @Override
+                        public Enumeration<String> run() {
+                            return doGetAttributeNamesInScope(scope);
+                        }
+                    });
+        } else {
+            return doGetAttributeNamesInScope(scope);
+        }
+    }
+
+    private Enumeration<String> doGetAttributeNamesInScope(int scope) {
+        switch (scope) {
+        case PAGE_SCOPE:
+            return new Enumerator<String>(attributes.keySet().iterator());
+
+        case REQUEST_SCOPE:
+            return request.getAttributeNames();
+
+        case SESSION_SCOPE:
+            if (session == null) {
+                throw new IllegalStateException(Localizer
+                        .getMessage("jsp.error.page.noSession"));
+            }
+            return session.getAttributeNames();
+
+        case APPLICATION_SCOPE:
+            return context.getAttributeNames();
+
+        default:
+            throw new IllegalArgumentException("Invalid scope");
+        }
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+
+        if (name == null) {
+            throw new NullPointerException(Localizer
+                    .getMessage("jsp.error.attribute.null_name"));
+        }
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    doRemoveAttribute(name);
+                    return null;
+                }
+            });
+        } else {
+            doRemoveAttribute(name);
+        }
+    }
+
+    private void doRemoveAttribute(String name) {
+        removeAttribute(name, PAGE_SCOPE);
+        removeAttribute(name, REQUEST_SCOPE);
+        if( session != null ) {
+            try {
+                removeAttribute(name, SESSION_SCOPE);
+            } catch(IllegalStateException ise) {
+                // Session has been invalidated.
+                // Ignore and fall throw to application scope.
+            }
+        }
+        removeAttribute(name, APPLICATION_SCOPE);
+    }
+
+    @Override
+    public JspWriter getOut() {
+        return out;
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return session;
+    }
+
+    @Override
+    public ServletConfig getServletConfig() {
+        return config;
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return config.getServletContext();
+    }
+
+    @Override
+    public ServletRequest getRequest() {
+        return request;
+    }
+
+    @Override
+    public ServletResponse getResponse() {
+        return response;
+    }
+
+    /**
+     * Returns the exception associated with this page context, if any. <p/>
+     * Added wrapping for Throwables to avoid ClassCastException: see Bugzilla
+     * 31171 for details.
+     * 
+     * @return The Exception associated with this page context, if any.
+     */
+    @Override
+    public Exception getException() {
+        Throwable t = JspRuntimeLibrary.getThrowable(request);
+
+        // Only wrap if needed
+        if ((t != null) && (!(t instanceof Exception))) {
+            t = new JspException(t);
+        }
+
+        return (Exception) t;
+    }
+
+    @Override
+    public Object getPage() {
+        return servlet;
+    }
+
+    private final String getAbsolutePathRelativeToContext(String relativeUrlPath) {
+        String path = relativeUrlPath;
+
+        if (!path.startsWith("/")) {
+            String uri = (String) request.getAttribute(
+                    RequestDispatcher.INCLUDE_SERVLET_PATH);
+            if (uri == null)
+                uri = ((HttpServletRequest) request).getServletPath();
+            String baseURI = uri.substring(0, uri.lastIndexOf('/'));
+            path = baseURI + '/' + path;
+        }
+
+        return path;
+    }
+
+    @Override
+    public void include(String relativeUrlPath) throws ServletException,
+            IOException {
+        JspRuntimeLibrary
+                .include(request, response, relativeUrlPath, out, true);
+    }
+
+    @Override
+    public void include(final String relativeUrlPath, final boolean flush)
+            throws ServletException, IOException {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Void>() {
+                    @Override
+                    public Void run() throws Exception {
+                        doInclude(relativeUrlPath, flush);
+                        return null;
+                    }
+                });
+            } catch (PrivilegedActionException e) {
+                Exception ex = e.getException();
+                if (ex instanceof IOException) {
+                    throw (IOException) ex;
+                } else {
+                    throw (ServletException) ex;
+                }
+            }
+        } else {
+            doInclude(relativeUrlPath, flush);
+        }
+    }
+
+    private void doInclude(String relativeUrlPath, boolean flush)
+            throws ServletException, IOException {
+        JspRuntimeLibrary.include(request, response, relativeUrlPath, out,
+                flush);
+    }
+
+    @Override
+    @Deprecated
+    public VariableResolver getVariableResolver() {
+        return new VariableResolverImpl(this.getELContext());
+    }
+
+    @Override
+    public void forward(final String relativeUrlPath) throws ServletException,
+            IOException {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Void>() {
+                    @Override
+                    public Void run() throws Exception {
+                        doForward(relativeUrlPath);
+                        return null;
+                    }
+                });
+            } catch (PrivilegedActionException e) {
+                Exception ex = e.getException();
+                if (ex instanceof IOException) {
+                    throw (IOException) ex;
+                } else {
+                    throw (ServletException) ex;
+                }
+            }
+        } else {
+            doForward(relativeUrlPath);
+        }
+    }
+
+    private void doForward(String relativeUrlPath) throws ServletException,
+            IOException {
+
+        // JSP.4.5 If the buffer was flushed, throw IllegalStateException
+        try {
+            out.clear();
+        } catch (IOException ex) {
+            IllegalStateException ise = new IllegalStateException(Localizer
+                    .getMessage("jsp.error.attempt_to_clear_flushed_buffer"));
+            ise.initCause(ex);
+            throw ise;
+        }
+
+        // Make sure that the response object is not the wrapper for include
+        while (response instanceof ServletResponseWrapperInclude) {
+            response = ((ServletResponseWrapperInclude) response).getResponse();
+        }
+
+        final String path = getAbsolutePathRelativeToContext(relativeUrlPath);
+        String includeUri = (String) request.getAttribute(
+                RequestDispatcher.INCLUDE_SERVLET_PATH);
+
+        if (includeUri != null)
+            request.removeAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+        try {
+            context.getRequestDispatcher(path).forward(request, response);
+        } finally {
+            if (includeUri != null)
+                request.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,
+                        includeUri);
+        }
+    }
+
+    @Override
+    public BodyContent pushBody() {
+        return (BodyContent) pushBody(null);
+    }
+
+    @Override
+    public JspWriter pushBody(Writer writer) {
+        depth++;
+        if (depth >= outs.length) {
+            BodyContentImpl[] newOuts = new BodyContentImpl[depth + 1];
+            for (int i = 0; i < outs.length; i++) {
+                newOuts[i] = outs[i];
+            }
+            newOuts[depth] = new BodyContentImpl(out);
+            outs = newOuts;
+        }
+
+        outs[depth].setWriter(writer);
+        out = outs[depth];
+
+        // Update the value of the "out" attribute in the page scope
+        // attribute namespace of this PageContext
+        setAttribute(OUT, out);
+
+        return outs[depth];
+    }
+
+    @Override
+    public JspWriter popBody() {
+        depth--;
+        if (depth >= 0) {
+            out = outs[depth];
+        } else {
+            out = baseOut;
+        }
+
+        // Update the value of the "out" attribute in the page scope
+        // attribute namespace of this PageContext
+        setAttribute(OUT, out);
+
+        return out;
+    }
+
+    /**
+     * Provides programmatic access to the ExpressionEvaluator. The JSP
+     * Container must return a valid instance of an ExpressionEvaluator that can
+     * parse EL expressions.
+     */
+    @Override
+    @Deprecated
+    public ExpressionEvaluator getExpressionEvaluator() {
+        return new ExpressionEvaluatorImpl(this.applicationContext.getExpressionFactory());
+    }
+
+    @Override
+    public void handlePageException(Exception ex) throws IOException,
+            ServletException {
+        // Should never be called since handleException() called with a
+        // Throwable in the generated servlet.
+        handlePageException((Throwable) ex);
+    }
+
+    @Override
+    public void handlePageException(final Throwable t) throws IOException,
+            ServletException {
+        if (t == null)
+            throw new NullPointerException("null Throwable");
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Void>() {
+                    @Override
+                    public Void run() throws Exception {
+                        doHandlePageException(t);
+                        return null;
+                    }
+                });
+            } catch (PrivilegedActionException e) {
+                Exception ex = e.getException();
+                if (ex instanceof IOException) {
+                    throw (IOException) ex;
+                } else {
+                    throw (ServletException) ex;
+                }
+            }
+        } else {
+            doHandlePageException(t);
+        }
+
+    }
+
+    private void doHandlePageException(Throwable t) throws IOException,
+            ServletException {
+
+        if (errorPageURL != null && !errorPageURL.equals("")) {
+
+            /*
+             * Set request attributes. Do not set the
+             * javax.servlet.error.exception attribute here (instead, set in the
+             * generated servlet code for the error page) in order to prevent
+             * the ErrorReportValve, which is invoked as part of forwarding the
+             * request to the error page, from throwing it if the response has
+             * not been committed (the response will have been committed if the
+             * error page is a JSP page).
+             */
+            request.setAttribute(PageContext.EXCEPTION, t);
+            request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
+                    new Integer(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
+            request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,
+                    ((HttpServletRequest) request).getRequestURI());
+            request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,
+                    config.getServletName());
+            try {
+                forward(errorPageURL);
+            } catch (IllegalStateException ise) {
+                include(errorPageURL);
+            }
+
+            // The error page could be inside an include.
+
+            Object newException =
+                    request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+
+            // t==null means the attribute was not set.
+            if ((newException != null) && (newException == t)) {
+                request.removeAttribute(RequestDispatcher.ERROR_EXCEPTION);
+            }
+
+            // now clear the error code - to prevent double handling.
+            request.removeAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+            request.removeAttribute(RequestDispatcher.ERROR_REQUEST_URI);
+            request.removeAttribute(RequestDispatcher.ERROR_SERVLET_NAME);
+            request.removeAttribute(PageContext.EXCEPTION);
+
+        } else {
+            // Otherwise throw the exception wrapped inside a ServletException.
+            // Set the exception as the root cause in the ServletException
+            // to get a stack trace for the real problem
+            if (t instanceof IOException)
+                throw (IOException) t;
+            if (t instanceof ServletException)
+                throw (ServletException) t;
+            if (t instanceof RuntimeException)
+                throw (RuntimeException) t;
+
+            Throwable rootCause = null;
+            if (t instanceof JspException) {
+                rootCause = ((JspException) t).getCause();
+            } else if (t instanceof ELException) {
+                rootCause = ((ELException) t).getCause();
+            }
+
+            if (rootCause != null) {
+                throw new ServletException(t.getClass().getName() + ": "
+                        + t.getMessage(), rootCause);
+            }
+
+            throw new ServletException(t);
+        }
+    }
+
+    private static String XmlEscape(String s) {
+        if (s == null)
+            return null;
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '<') {
+                sb.append("&lt;");
+            } else if (c == '>') {
+                sb.append("&gt;");
+            } else if (c == '\'') {
+                sb.append("&#039;"); // &apos;
+            } else if (c == '&') {
+                sb.append("&amp;");
+            } else if (c == '"') {
+                sb.append("&#034;"); // &quot;
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Proprietary method to evaluate EL expressions. XXX - This method should
+     * go away once the EL interpreter moves out of JSTL and into its own
+     * project. For now, this is necessary because the standard machinery is too
+     * slow.
+     * 
+     * @param expression
+     *            The expression to be evaluated
+     * @param expectedType
+     *            The expected resulting type
+     * @param pageContext
+     *            The page context
+     * @param functionMap
+     *            Maps prefix and name to Method
+     * @return The result of the evaluation
+     */
+    public static Object proprietaryEvaluate(final String expression,
+            final Class<?> expectedType, final PageContext pageContext,
+            final ProtectedFunctionMapper functionMap, final boolean escape)
+            throws ELException {
+        Object retValue;
+        final ExpressionFactory exprFactory = jspf.getJspApplicationContext(pageContext.getServletContext()).getExpressionFactory();
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                retValue = AccessController
+                        .doPrivileged(new PrivilegedExceptionAction<Object>() {
+
+                            @Override
+                            public Object run() throws Exception {
+                                ELContextImpl ctx = (ELContextImpl) pageContext.getELContext();
+                                ctx.setFunctionMapper(new FunctionMapperImpl(functionMap));
+                                ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType);
+                                return ve.getValue(ctx);
+                            }
+                        });
+            } catch (PrivilegedActionException ex) {
+                Exception realEx = ex.getException();
+                if (realEx instanceof ELException) {
+                    throw (ELException) realEx;
+                } else {
+                    throw new ELException(realEx);
+                }
+            }
+        } else {
+            ELContextImpl ctx = (ELContextImpl) pageContext.getELContext();
+            ctx.setFunctionMapper(new FunctionMapperImpl(functionMap));
+            ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType);
+            retValue = ve.getValue(ctx);
+        }
+        if (escape && retValue != null) {
+            retValue = XmlEscape(retValue.toString());
+        }
+
+        return retValue;
+    }
+
+    @Override
+    public ELContext getELContext() {
+        if (this.elContext == null) {
+            this.elContext = this.applicationContext.createELContext(this);
+        }
+        return this.elContext;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/PerThreadTagHandlerPool.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/PerThreadTagHandlerPool.java
new file mode 100644
index 0000000..4ce4684
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/PerThreadTagHandlerPool.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.Tag;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Thread-local based pool of tag handlers that can be reused.
+ *
+ * @author Jan Luehe
+ * @author Costin Manolache
+ */
+public class PerThreadTagHandlerPool extends TagHandlerPool {
+
+    private int maxSize;
+
+    // For cleanup
+    private Vector<PerThreadData> perThreadDataVector;
+
+    private ThreadLocal<PerThreadData> perThread;
+
+    private static class PerThreadData {
+        Tag handlers[];
+        int current;
+    }
+
+    /**
+     * Constructs a tag handler pool with the default capacity.
+     */
+    public PerThreadTagHandlerPool() {
+        super();
+        perThreadDataVector = new Vector<PerThreadData>();
+    }
+
+    @Override
+    protected void init(ServletConfig config) {
+        maxSize = Constants.MAX_POOL_SIZE;
+        String maxSizeS = getOption(config, OPTION_MAXSIZE, null);
+        if (maxSizeS != null) {
+            maxSize = Integer.parseInt(maxSizeS);
+            if (maxSize < 0) {
+                maxSize = Constants.MAX_POOL_SIZE;
+            }
+        }
+
+        perThread = new ThreadLocal<PerThreadData>() {
+            @Override
+            protected PerThreadData initialValue() {
+                PerThreadData ptd = new PerThreadData();
+                ptd.handlers = new Tag[maxSize];
+                ptd.current = -1;
+                perThreadDataVector.addElement(ptd);
+                return ptd;
+            }
+        };
+    }
+
+    /**
+     * Gets the next available tag handler from this tag handler pool,
+     * instantiating one if this tag handler pool is empty.
+     *
+     * @param handlerClass Tag handler class
+     *
+     * @return Reused or newly instantiated tag handler
+     *
+     * @throws JspException if a tag handler cannot be instantiated
+     */
+    @Override
+    public Tag get(Class<? extends Tag> handlerClass) throws JspException {
+        PerThreadData ptd = perThread.get();
+        if(ptd.current >=0 ) {
+            return ptd.handlers[ptd.current--];
+        } else {
+            try {
+                return handlerClass.newInstance();
+            } catch (Exception e) {
+                throw new JspException(e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * Adds the given tag handler to this tag handler pool, unless this tag
+     * handler pool has already reached its capacity, in which case the tag
+     * handler's release() method is called.
+     *
+     * @param handler Tag handler to add to this tag handler pool
+     */
+    @Override
+    public void reuse(Tag handler) {
+        PerThreadData ptd = perThread.get();
+        if (ptd.current < (ptd.handlers.length - 1)) {
+            ptd.handlers[++ptd.current] = handler;
+        } else {
+            handler.release();
+        }
+    }
+
+    /**
+     * Calls the release() method of all tag handlers in this tag handler pool.
+     */
+    @Override
+    public void release() {        
+        Enumeration<PerThreadData> enumeration = perThreadDataVector.elements();
+        while (enumeration.hasMoreElements()) {
+            PerThreadData ptd = enumeration.nextElement();
+            if (ptd.handlers != null) {
+                for (int i=ptd.current; i>=0; i--) {
+                    if (ptd.handlers[i] != null) {
+                        ptd.handlers[i].release();
+                    }
+                }
+            }
+        }
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/ProtectedFunctionMapper.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/ProtectedFunctionMapper.java
new file mode 100644
index 0000000..dca4bd9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/ProtectedFunctionMapper.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+
+import javax.servlet.jsp.el.FunctionMapper;
+
+import org.apache.jasper.security.SecurityUtil;
+
+/**
+ * Maps EL functions to their Java method counterparts. Keeps the actual Method
+ * objects protected so that JSP pages can't indirectly do reflection.
+ * 
+ * @author Mark Roth
+ * @author Kin-man Chung
+ */
+public final class ProtectedFunctionMapper extends javax.el.FunctionMapper
+        implements FunctionMapper {
+
+    /**
+     * Maps "prefix:name" to java.lang.Method objects.
+     */
+    private HashMap<String,Method> fnmap = null;
+
+    /**
+     * If there is only one function in the map, this is the Method for it.
+     */
+    private Method theMethod = null;
+
+    /**
+     * Constructor has protected access.
+     */
+    private ProtectedFunctionMapper() {
+    }
+
+    /**
+     * Generated Servlet and Tag Handler implementations call this method to
+     * retrieve an instance of the ProtectedFunctionMapper. This is necessary
+     * since generated code does not have access to create instances of classes
+     * in this package.
+     * 
+     * @return A new protected function mapper.
+     */
+    public static ProtectedFunctionMapper getInstance() {
+        ProtectedFunctionMapper funcMapper;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            funcMapper = AccessController.doPrivileged(
+                    new PrivilegedAction<ProtectedFunctionMapper>() {
+                        @Override
+                        public ProtectedFunctionMapper run() {
+                            return new ProtectedFunctionMapper();
+                        }
+                    });
+        } else {
+            funcMapper = new ProtectedFunctionMapper();
+        }
+        funcMapper.fnmap = new HashMap<String,Method>();
+        return funcMapper;
+    }
+
+    /**
+     * Stores a mapping from the given EL function prefix and name to the given
+     * Java method.
+     * 
+     * @param fnQName
+     *            The EL function qualified name (including prefix)
+     * @param c
+     *            The class containing the Java method
+     * @param methodName
+     *            The name of the Java method
+     * @param args
+     *            The arguments of the Java method
+     * @throws RuntimeException
+     *             if no method with the given signature could be found.
+     */
+    public void mapFunction(String fnQName, final Class<?> c,
+            final String methodName, final Class<?>[] args) {
+        java.lang.reflect.Method method;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                method = AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Method>() {
+                            @Override
+                            public Method run() throws Exception {
+                                return c.getDeclaredMethod(methodName, args);
+                            }
+                        });
+            } catch (PrivilegedActionException ex) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + ex.getException().getMessage());
+            }
+        } else {
+            try {
+                method = c.getDeclaredMethod(methodName, args);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + e.getMessage());
+            }
+        }
+
+        this.fnmap.put(fnQName, method);
+    }
+
+    /**
+     * Creates an instance for this class, and stores the Method for the given
+     * EL function prefix and name. This method is used for the case when there
+     * is only one function in the EL expression.
+     * 
+     * @param fnQName
+     *            The EL function qualified name (including prefix)
+     * @param c
+     *            The class containing the Java method
+     * @param methodName
+     *            The name of the Java method
+     * @param args
+     *            The arguments of the Java method
+     * @throws RuntimeException
+     *             if no method with the given signature could be found.
+     */
+    public static ProtectedFunctionMapper getMapForFunction(String fnQName,
+            final Class<?> c, final String methodName, final Class<?>[] args) {
+        java.lang.reflect.Method method;
+        ProtectedFunctionMapper funcMapper;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            funcMapper = AccessController.doPrivileged(
+                    new PrivilegedAction<ProtectedFunctionMapper>() {
+                        @Override
+                        public ProtectedFunctionMapper run() {
+                            return new ProtectedFunctionMapper();
+                        }
+                    });
+
+            try {
+                method = AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Method>() {
+                            @Override
+                            public Method run() throws Exception {
+                                return c.getDeclaredMethod(methodName, args);
+                            }
+                        });
+            } catch (PrivilegedActionException ex) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + ex.getException().getMessage());
+            }
+        } else {
+            funcMapper = new ProtectedFunctionMapper();
+            try {
+                method = c.getDeclaredMethod(methodName, args);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException(
+                        "Invalid function mapping - no such method: "
+                                + e.getMessage());
+            }
+        }
+        funcMapper.theMethod = method;
+        return funcMapper;
+    }
+
+    /**
+     * Resolves the specified local name and prefix into a Java.lang.Method.
+     * Returns null if the prefix and local name are not found.
+     * 
+     * @param prefix
+     *            the prefix of the function
+     * @param localName
+     *            the short name of the function
+     * @return the result of the method mapping. Null means no entry found.
+     */
+    @Override
+    @Deprecated
+    public Method resolveFunction(String prefix, String localName) {
+        if (this.fnmap != null) {
+            return this.fnmap.get(prefix + ":" + localName);
+        }
+        return theMethod;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/ServletResponseWrapperInclude.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/ServletResponseWrapperInclude.java
new file mode 100644
index 0000000..0fa695d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/ServletResponseWrapperInclude.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspWriter;
+
+/**
+ * ServletResponseWrapper used by the JSP 'include' action.
+ *
+ * This wrapper response object is passed to RequestDispatcher.include(), so
+ * that the output of the included resource is appended to that of the
+ * including page.
+ *
+ * @author Pierre Delisle
+ */
+
+public class ServletResponseWrapperInclude extends HttpServletResponseWrapper {
+
+    /**
+     * PrintWriter which appends to the JspWriter of the including page.
+     */
+    private PrintWriter printWriter;
+
+    private JspWriter jspWriter;
+
+    public ServletResponseWrapperInclude(ServletResponse response, 
+                                         JspWriter jspWriter) {
+        super((HttpServletResponse)response);
+        this.printWriter = new PrintWriter(jspWriter);
+        this.jspWriter = jspWriter;
+    }
+
+    /**
+     * Returns a wrapper around the JspWriter of the including page.
+     */
+    @Override
+    public PrintWriter getWriter() throws IOException {
+        return printWriter;
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Clears the output buffer of the JspWriter associated with the including
+     * page.
+     */
+    @Override
+    public void resetBuffer() {
+        try {
+            jspWriter.clearBuffer();
+        } catch (IOException ioe) {
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/TagHandlerPool.java b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/TagHandlerPool.java
new file mode 100644
index 0000000..3c7aafb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/runtime/TagHandlerPool.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.runtime;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.Tag;
+
+import org.apache.jasper.Constants;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.InstanceManager;
+
+/**
+ * Pool of tag handlers that can be reused.
+ *
+ * @author Jan Luehe
+ */
+public class TagHandlerPool {
+
+    private Tag[] handlers;
+
+    public static final String OPTION_TAGPOOL="tagpoolClassName";
+    public static final String OPTION_MAXSIZE="tagpoolMaxSize";
+
+    private final Log log = LogFactory.getLog(TagHandlerPool.class);
+    
+    // index of next available tag handler
+    private int current;
+    protected InstanceManager instanceManager = null;
+
+    public static TagHandlerPool getTagHandlerPool( ServletConfig config) {
+        TagHandlerPool result=null;
+
+        String tpClassName=getOption( config, OPTION_TAGPOOL, null);
+        if( tpClassName != null ) {
+            try {
+                Class<?> c=Class.forName( tpClassName );
+                result=(TagHandlerPool)c.newInstance();
+            } catch (Exception e) {
+                e.printStackTrace();
+                result=null;
+            }
+        }
+        if( result==null ) result=new TagHandlerPool();
+        result.init(config);
+
+        return result;
+    }
+
+    protected void init( ServletConfig config ) {
+        int maxSize=-1;
+        String maxSizeS=getOption(config, OPTION_MAXSIZE, null);
+        if( maxSizeS != null ) {
+            try {
+                maxSize=Integer.parseInt(maxSizeS);
+            } catch( Exception ex) {
+                maxSize=-1;
+            }
+        }
+        if( maxSize <0  ) {
+            maxSize=Constants.MAX_POOL_SIZE;
+        }
+        this.handlers = new Tag[maxSize];
+        this.current = -1;
+        instanceManager = InstanceManagerFactory.getInstanceManager(config);
+    }
+
+    /**
+     * Constructs a tag handler pool with the default capacity.
+     */
+    public TagHandlerPool() {
+        // Nothing - jasper generated servlets call the other constructor,
+        // this should be used in future + init .
+    }
+
+    /**
+     * Gets the next available tag handler from this tag handler pool,
+     * instantiating one if this tag handler pool is empty.
+     *
+     * @param handlerClass Tag handler class
+     *
+     * @return Reused or newly instantiated tag handler
+     *
+     * @throws JspException if a tag handler cannot be instantiated
+     */
+    public Tag get(Class<? extends Tag> handlerClass) throws JspException {
+        Tag handler;
+        synchronized( this ) {
+            if (current >= 0) {
+                handler = handlers[current--];
+                return handler;
+            }
+        }
+
+        // Out of sync block - there is no need for other threads to
+        // wait for us to construct a tag for this thread.
+        try {
+            if (Constants.USE_INSTANCE_MANAGER_FOR_TAGS) {
+                return (Tag) instanceManager.newInstance(handlerClass.getName(), handlerClass.getClassLoader());
+            } else {
+                Tag instance = handlerClass.newInstance();
+                instanceManager.newInstance(instance);
+                return instance;
+            }
+        } catch (Exception e) {
+            throw new JspException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Adds the given tag handler to this tag handler pool, unless this tag
+     * handler pool has already reached its capacity, in which case the tag
+     * handler's release() method is called.
+     *
+     * @param handler Tag handler to add to this tag handler pool
+     */
+    public void reuse(Tag handler) {
+        synchronized( this ) {
+            if (current < (handlers.length - 1)) {
+                handlers[++current] = handler;
+                return;
+            }
+        }
+        // There is no need for other threads to wait for us to release
+        handler.release();
+        try {
+            instanceManager.destroyInstance(handler);
+        } catch (Exception e) {
+            log.warn("Error processing preDestroy on tag instance of "
+                    + handler.getClass().getName(), e);
+        }
+    }
+
+    /**
+     * Calls the release() method of all available tag handlers in this tag
+     * handler pool.
+     */
+    public synchronized void release() {
+        for (int i = current; i >= 0; i--) {
+            handlers[i].release();
+            try {
+                instanceManager.destroyInstance(handlers[i]);
+            } catch (Exception e) {
+                log.warn("Error processing preDestroy on tag instance of "
+                        + handlers[i].getClass().getName(), e);
+            }
+        }
+    }
+
+    protected static String getOption( ServletConfig config, String name, String defaultV) {
+        if( config == null ) return defaultV;
+
+        String value=config.getInitParameter(name);
+        if( value != null ) return value;
+        if( config.getServletContext() ==null )
+            return defaultV;
+        value=config.getServletContext().getInitParameter(name);
+        if( value!=null ) return value;
+        return defaultV;
+    }
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/security/SecurityClassLoad.java b/bundles/org.apache.tomcat/src/org/apache/jasper/security/SecurityClassLoad.java
new file mode 100644
index 0000000..5f5e56f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/security/SecurityClassLoad.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.security;
+
+/**
+ * Static class used to preload java classes when using the
+ * Java SecurityManager so that the defineClassInPackage
+ * RuntimePermission does not trigger an AccessControlException.
+ *
+ * @author Jean-Francois Arcand
+ */
+
+public final class SecurityClassLoad {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SecurityClassLoad.class );
+
+    public static void securityClassLoad(ClassLoader loader){
+
+        if( System.getSecurityManager() == null ){
+            return;
+        }
+
+        final String basePackage = "org.apache.jasper.";
+        try {
+            loader.loadClass( basePackage +
+                "runtime.JspFactoryImpl$PrivilegedGetPageContext");
+            loader.loadClass( basePackage +
+                "runtime.JspFactoryImpl$PrivilegedReleasePageContext");
+
+            loader.loadClass( basePackage +
+                "runtime.JspRuntimeLibrary");
+            loader.loadClass( basePackage +
+                "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper");
+            
+            loader.loadClass( basePackage +
+                "runtime.ServletResponseWrapperInclude");
+            loader.loadClass( basePackage +
+                "runtime.TagHandlerPool");
+            loader.loadClass( basePackage +
+                "runtime.JspFragmentHelper");
+
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper");
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$1");
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$2"); 
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$3");
+            loader.loadClass( basePackage +
+                "runtime.ProtectedFunctionMapper$4"); 
+
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$1");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$2");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$3");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$4");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$5");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$6");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$7");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$8");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$9");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$10");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$11");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$12");      
+            loader.loadClass( basePackage +
+                "runtime.PageContextImpl$13");      
+
+            loader.loadClass( basePackage +
+                "runtime.JspContextWrapper");   
+
+            // Trigger loading of class and reading of property
+            SecurityUtil.isPackageProtectionEnabled();
+            
+            loader.loadClass( basePackage +
+                "servlet.JspServletWrapper");
+
+            loader.loadClass( basePackage +
+                "runtime.JspWriterImpl$1");
+        } catch (ClassNotFoundException ex) {
+            log.error("SecurityClassLoad", ex);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/security/SecurityUtil.java b/bundles/org.apache.tomcat/src/org/apache/jasper/security/SecurityUtil.java
new file mode 100644
index 0000000..fc3a4a6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/security/SecurityUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.security;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Util class for Security related operations.
+ *
+ * @author Jean-Francois Arcand
+ */
+
+public final class SecurityUtil{
+    
+    private static boolean packageDefinitionEnabled =  
+         System.getProperty("package.definition") == null ? false : true;
+    
+    /**
+     * Return the <code>SecurityManager</code> only if Security is enabled AND
+     * package protection mechanism is enabled.
+     */
+    public static boolean isPackageProtectionEnabled(){
+        if (packageDefinitionEnabled && Constants.IS_SECURITY_ENABLED){
+            return true;
+        }
+        return false;
+    }
+    
+
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param message The message string to be filtered
+     */
+    public static String filter(String message) {
+
+        if (message == null)
+            return (null);
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+        StringBuilder result = new StringBuilder(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JasperLoader.java b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JasperLoader.java
new file mode 100644
index 0000000..731d3c4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JasperLoader.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Class loader for loading servlet class files (corresponding to JSP files) 
+ * and tag handler class files (corresponding to tag files).
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Jean-Francois Arcand
+ */
+public class JasperLoader extends URLClassLoader {
+
+    private PermissionCollection permissionCollection;
+    private ClassLoader parent;
+    private SecurityManager securityManager;
+
+    public JasperLoader(URL[] urls, ClassLoader parent,
+                        PermissionCollection permissionCollection) {
+        super(urls, parent);
+        this.permissionCollection = permissionCollection;
+        this.parent = parent;
+        this.securityManager = System.getSecurityManager();
+    }
+
+    /**
+     * Load the class with the specified name.  This method searches for
+     * classes in the same manner as <code>loadClass(String, boolean)</code>
+     * with <code>false</code> as the second argument.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+
+        return (loadClass(name, false));
+    }
+
+    /**
+     * Load the class with the specified name, searching using the following
+     * algorithm until it finds and returns the class.  If the class cannot
+     * be found, returns <code>ClassNotFoundException</code>.
+     * <ul>
+     * <li>Call <code>findLoadedClass(String)</code> to check if the
+     *     class has already been loaded.  If it has, the same
+     *     <code>Class</code> object is returned.</li>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>loadClass()</code> method of the parent class
+     *     loader, if any.</li>            
+     * <li>Call <code>findClass()</code> to find this class in our locally
+     *     defined repositories.</li>      
+     * <li>Call the <code>loadClass()</code> method of our parent
+     *     class loader, if any.</li>      
+     * </ul>
+     * If the class was found using the above steps, and the
+     * <code>resolve</code> flag is <code>true</code>, this method will then
+     * call <code>resolveClass(Class)</code> on the resulting Class object.
+     *                                     
+     * @param name Name of the class to be loaded
+     * @param resolve If <code>true</code> then resolve the class
+     *                                     
+     * @exception ClassNotFoundException if the class was not found
+     */                                    
+    @Override
+    public synchronized Class<?> loadClass(final String name, boolean resolve)
+        throws ClassNotFoundException {
+
+        Class<?> clazz = null;                
+                                           
+        // (0) Check our previously loaded class cache
+        clazz = findLoadedClass(name);     
+        if (clazz != null) {               
+            if (resolve)                   
+                resolveClass(clazz);       
+            return (clazz);        
+        }                          
+                          
+        // (.5) Permission to access this class when using a SecurityManager
+        if (securityManager != null) {     
+            int dot = name.lastIndexOf('.');
+            if (dot >= 0) {                
+                try {        
+                    // Do not call the security manager since by default, we grant that package.
+                    if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){
+                        securityManager.checkPackageAccess(name.substring(0,dot));
+                    }
+                } catch (SecurityException se) {
+                    String error = "Security Violation, attempt to use " +
+                        "Restricted Class: " + name;
+                    se.printStackTrace();
+                    throw new ClassNotFoundException(error);
+                }                          
+            }                              
+        }
+
+        if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) {
+            // Class is not in org.apache.jsp, therefore, have our
+            // parent load it
+            clazz = parent.loadClass(name);            
+            if( resolve )
+                resolveClass(clazz);
+            return clazz;
+        }
+
+        return findClass(name);
+    }
+
+    
+    /**
+     * Delegate to parent
+     * 
+     * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
+     */
+    @Override
+    public InputStream getResourceAsStream(String name) {
+        InputStream is = parent.getResourceAsStream(name);
+        if (is == null) {
+            URL url = findResource(name);
+            if (url != null) {
+                try {
+                    is = url.openStream();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return is;
+    }
+    
+    
+    /**
+     * Get the Permissions for a CodeSource.
+     *
+     * Since this ClassLoader is only used for a JSP page in
+     * a web application context, we just return our preset
+     * PermissionCollection for the web app context.
+     *
+     * @param codeSource Code source where the code was loaded from
+     * @return PermissionCollection for CodeSource
+     */
+    @Override
+    public final PermissionCollection getPermissions(CodeSource codeSource) {
+        return permissionCollection;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspCServletContext.java b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspCServletContext.java
new file mode 100644
index 0000000..ec97119
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspCServletContext.java
@@ -0,0 +1,648 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.servlet;
+
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+import org.apache.jasper.util.ExceptionUtils;
+
+
+/**
+ * Simple <code>ServletContext</code> implementation without
+ * HTTP-specific methods.
+ *
+ * @author Peter Rossbach (pr@webapp.de)
+ */
+
+public class JspCServletContext implements ServletContext {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Servlet context attributes.
+     */
+    protected Hashtable<String,Object> myAttributes;
+
+
+    /**
+     * The log writer we will write log messages to.
+     */
+    protected PrintWriter myLogWriter;
+
+
+    /**
+     * The base URL (document root) for this context.
+     */
+    protected URL myResourceBaseURL;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new instance of this ServletContext implementation.
+     *
+     * @param aLogWriter PrintWriter which is used for <code>log()</code> calls
+     * @param aResourceBaseURL Resource base URL
+     */
+    public JspCServletContext(PrintWriter aLogWriter, URL aResourceBaseURL) {
+
+        myAttributes = new Hashtable<String,Object>();
+        myLogWriter = aLogWriter;
+        myResourceBaseURL = aResourceBaseURL;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the specified context attribute, if any.
+     *
+     * @param name Name of the requested attribute
+     */
+    @Override
+    public Object getAttribute(String name) {
+
+        return (myAttributes.get(name));
+
+    }
+
+
+    /**
+     * Return an enumeration of context attribute names.
+     */
+    @Override
+    public Enumeration<String> getAttributeNames() {
+
+        return (myAttributes.keys());
+
+    }
+
+
+    /**
+     * Return the servlet context for the specified path.
+     *
+     * @param uripath Server-relative path starting with '/'
+     */
+    @Override
+    public ServletContext getContext(String uripath) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the context path.
+     */
+    @Override
+    public String getContextPath() {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the specified context initialization parameter.
+     *
+     * @param name Name of the requested parameter
+     */
+    @Override
+    public String getInitParameter(String name) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return an enumeration of the names of context initialization
+     * parameters.
+     */
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+
+        return (new Vector<String>().elements());
+
+    }
+
+
+    /**
+     * Return the Servlet API major version number.
+     */
+    @Override
+    public int getMajorVersion() {
+
+        return (3);
+
+    }
+
+
+    /**
+     * Return the MIME type for the specified filename.
+     *
+     * @param file Filename whose MIME type is requested
+     */
+    @Override
+    public String getMimeType(String file) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the Servlet API minor version number.
+     */
+    @Override
+    public int getMinorVersion() {
+
+        return (0);
+
+    }
+
+
+    /**
+     * Return a request dispatcher for the specified servlet name.
+     *
+     * @param name Name of the requested servlet
+     */
+    @Override
+    public RequestDispatcher getNamedDispatcher(String name) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the real path for the specified context-relative
+     * virtual path.
+     *
+     * @param path The context-relative virtual path to resolve
+     */
+    @Override
+    public String getRealPath(String path) {
+
+        if (!myResourceBaseURL.getProtocol().equals("file"))
+            return (null);
+        if (!path.startsWith("/"))
+            return (null);
+        try {
+            return
+                (getResource(path).getFile().replace('/', File.separatorChar));
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            return (null);
+        }
+
+    }
+            
+            
+    /**
+     * Return a request dispatcher for the specified context-relative path.
+     *
+     * @param path Context-relative path for which to acquire a dispatcher
+     */
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return a URL object of a resource that is mapped to the
+     * specified context-relative path.
+     *
+     * @param path Context-relative path of the desired resource
+     *
+     * @exception MalformedURLException if the resource path is
+     *  not properly formed
+     */
+    @Override
+    public URL getResource(String path) throws MalformedURLException {
+
+        if (!path.startsWith("/"))
+            throw new MalformedURLException("Path '" + path +
+                                            "' does not start with '/'");
+        URL url = new URL(myResourceBaseURL, path.substring(1));
+        InputStream is = null;
+        try {
+            is = url.openStream();
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            url = null;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (Throwable t2) {
+                    ExceptionUtils.handleThrowable(t2);
+                }
+            }
+        }
+        return url;
+
+    }
+
+
+    /**
+     * Return an InputStream allowing access to the resource at the
+     * specified context-relative path.
+     *
+     * @param path Context-relative path of the desired resource
+     */
+    @Override
+    public InputStream getResourceAsStream(String path) {
+
+        try {
+            return (getResource(path).openStream());
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Return the set of resource paths for the "directory" at the
+     * specified context path.
+     *
+     * @param path Context-relative base path
+     */
+    @Override
+    public Set<String> getResourcePaths(String path) {
+
+        Set<String> thePaths = new HashSet<String>();
+        if (!path.endsWith("/"))
+            path += "/";
+        String basePath = getRealPath(path);
+        if (basePath == null)
+            return (thePaths);
+        File theBaseDir = new File(basePath);
+        if (!theBaseDir.exists() || !theBaseDir.isDirectory())
+            return (thePaths);
+        String theFiles[] = theBaseDir.list();
+        for (int i = 0; i < theFiles.length; i++) {
+            File testFile = new File(basePath + File.separator + theFiles[i]);
+            if (testFile.isFile())
+                thePaths.add(path + theFiles[i]);
+            else if (testFile.isDirectory())
+                thePaths.add(path + theFiles[i] + "/");
+        }
+        return (thePaths);
+
+    }
+
+
+    /**
+     * Return descriptive information about this server.
+     */
+    @Override
+    public String getServerInfo() {
+
+        return ("JspCServletContext/1.0");
+
+    }
+
+
+    /**
+     * Return a null reference for the specified servlet name.
+     *
+     * @param name Name of the requested servlet
+     *
+     * @deprecated This method has been deprecated with no replacement
+     */
+    @Override
+    @Deprecated
+    public Servlet getServlet(String name) throws ServletException {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the name of this servlet context.
+     */
+    @Override
+    public String getServletContextName() {
+
+        return (getServerInfo());
+
+    }
+
+
+    /**
+     * Return an empty enumeration of servlet names.
+     *
+     * @deprecated This method has been deprecated with no replacement
+     */
+    @Override
+    @Deprecated
+    public Enumeration<String> getServletNames() {
+
+        return (new Vector<String>().elements());
+
+    }
+
+
+    /**
+     * Return an empty enumeration of servlets.
+     *
+     * @deprecated This method has been deprecated with no replacement
+     */
+    @Override
+    @Deprecated
+    public Enumeration<Servlet> getServlets() {
+
+        return (new Vector<Servlet>().elements());
+
+    }
+
+
+    /**
+     * Log the specified message.
+     *
+     * @param message The message to be logged
+     */
+    @Override
+    public void log(String message) {
+
+        myLogWriter.println(message);
+
+    }
+
+
+    /**
+     * Log the specified message and exception.
+     *
+     * @param exception The exception to be logged
+     * @param message The message to be logged
+     *
+     * @deprecated Use log(String,Throwable) instead
+     */
+    @Override
+    @Deprecated
+    public void log(Exception exception, String message) {
+
+        log(message, exception);
+
+    }
+
+
+    /**
+     * Log the specified message and exception.
+     *
+     * @param message The message to be logged
+     * @param exception The exception to be logged
+     */
+    @Override
+    public void log(String message, Throwable exception) {
+
+        myLogWriter.println(message);
+        exception.printStackTrace(myLogWriter);
+
+    }
+
+
+    /**
+     * Remove the specified context attribute.
+     *
+     * @param name Name of the attribute to remove
+     */
+    @Override
+    public void removeAttribute(String name) {
+
+        myAttributes.remove(name);
+
+    }
+
+
+    /**
+     * Set or replace the specified context attribute.
+     *
+     * @param name Name of the context attribute to set
+     * @param value Corresponding attribute value
+     */
+    @Override
+    public void setAttribute(String name, Object value) {
+
+        myAttributes.put(name, value);
+
+    }
+
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            String className) {
+        return null;
+    }
+
+
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            String className) {
+        return null;
+    }
+
+
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+        return EnumSet.noneOf(SessionTrackingMode.class);
+    }
+
+
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+        return EnumSet.noneOf(SessionTrackingMode.class);
+    }
+
+
+    @Override
+    public SessionCookieConfig getSessionCookieConfig() {
+        return null;
+    }
+
+
+    @Override
+    public void setSessionTrackingModes(
+            Set<SessionTrackingMode> sessionTrackingModes) {
+        // Do nothing
+    }
+
+
+    @Override
+    public Dynamic addFilter(String filterName, Filter filter) {
+        return null;
+    }
+
+
+    @Override
+    public Dynamic addFilter(String filterName,
+            Class<? extends Filter> filterClass) {
+        return null;
+    }
+
+
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Servlet servlet) {
+        return null;
+    }
+
+
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Class<? extends Servlet> servletClass) {
+        return null;
+    }
+
+
+    @Override
+    public <T extends Filter> T createFilter(Class<T> c)
+            throws ServletException {
+        return null;
+    }
+
+
+    @Override
+    public <T extends Servlet> T createServlet(Class<T> c)
+            throws ServletException {
+        return null;
+    }
+
+
+    @Override
+    public FilterRegistration getFilterRegistration(String filterName) {
+        return null;
+    }
+
+
+    @Override
+    public ServletRegistration getServletRegistration(String servletName) {
+        return null;
+    }
+
+
+    @Override
+    public boolean setInitParameter(String name, String value) {
+        return false;
+    }
+
+
+    @Override
+    public void addListener(Class<? extends EventListener> listenerClass) {
+        // NOOP
+    }
+
+
+    @Override
+    public void addListener(String className) {
+        // NOOP
+    }
+
+
+    @Override
+    public <T extends EventListener> void addListener(T t) {
+        // NOOP
+    }
+
+
+    @Override
+    public <T extends EventListener> T createListener(Class<T> c)
+            throws ServletException {
+        return null;
+    }
+
+
+    @Override
+    public void declareRoles(String... roleNames) {
+        // NOOP
+    }
+
+
+    @Override
+    public ClassLoader getClassLoader() {
+        return null;
+    }
+
+
+    @Override
+    public int getEffectiveMajorVersion() {
+        return 3;
+    }
+
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        return 0;
+    }
+
+
+    @Override
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+        return null;
+    }
+
+
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        return null;
+    }
+
+
+    @Override
+    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
+        return null;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspServlet.java b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspServlet.java
new file mode 100644
index 0000000..db0d51f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspServlet.java
@@ -0,0 +1,427 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.net.MalformedURLException;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+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.apache.jasper.Constants;
+import org.apache.jasper.EmbeddedServletOptions;
+import org.apache.jasper.Options;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.security.SecurityUtil;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.PeriodicEventListener;
+
+/**
+ * The JSP engine (a.k.a Jasper).
+ *
+ * The servlet container is responsible for providing a
+ * URLClassLoader for the web application context Jasper
+ * is being used in. Jasper will try get the Tomcat
+ * ServletContext attribute for its ServletContext class
+ * loader, if that fails, it uses the parent class loader.
+ * In either case, it must be a URLClassLoader.
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Remy Maucherat
+ * @author Kin-man Chung
+ * @author Glenn Nielsen
+ */
+public class JspServlet extends HttpServlet implements PeriodicEventListener {
+
+    private static final long serialVersionUID = 1L;
+
+    // Logger
+    private final transient Log log = LogFactory.getLog(JspServlet.class);
+
+    private transient ServletContext context;
+    private ServletConfig config;
+    private transient Options options;
+    private transient JspRuntimeContext rctxt;
+    //jspFile for a jsp configured explicitly as a servlet, in environments where this configuration is
+    //translated into an init-param for this servlet.
+    private String jspFile;
+
+
+    /*
+     * Initializes this JspServlet.
+     */
+    @Override
+    public void init(ServletConfig config) throws ServletException {
+        
+        super.init(config);
+        this.config = config;
+        this.context = config.getServletContext();
+        
+        // Initialize the JSP Runtime Context
+        // Check for a custom Options implementation
+        String engineOptionsName = 
+            config.getInitParameter("engineOptionsClass");
+        if (engineOptionsName != null) {
+            // Instantiate the indicated Options implementation
+            try {
+                ClassLoader loader = Thread.currentThread()
+                        .getContextClassLoader();
+                Class<?> engineOptionsClass =
+                    loader.loadClass(engineOptionsName);
+                Class<?>[] ctorSig =
+                    { ServletConfig.class, ServletContext.class };
+                Constructor<?> ctor =
+                    engineOptionsClass.getConstructor(ctorSig);
+                Object[] args = { config, context };
+                options = (Options) ctor.newInstance(args);
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                // Need to localize this.
+                log.warn("Failed to load engineOptionsClass", e);
+                // Use the default Options implementation
+                options = new EmbeddedServletOptions(config, context);
+            }
+        } else {
+            // Use the default Options implementation
+            options = new EmbeddedServletOptions(config, context);
+        }
+        rctxt = new JspRuntimeContext(context, options);
+        if (config.getInitParameter("jspFile") != null) {
+            jspFile = config.getInitParameter("jspFile");
+            try {
+                if (null == context.getResource(jspFile)) {
+                    throw new ServletException("missing jspFile");
+                }
+            } catch (MalformedURLException e) {
+                throw new ServletException("Can not locate jsp file", e);
+            }
+            try {
+                serviceJspFile(null, null, jspFile, null, true);
+                if (SecurityUtil.isPackageProtectionEnabled()){
+                   AccessController.doPrivileged(new PrivilegedExceptionAction<Object>(){
+                        @Override
+                        public Object run() throws IOException, ServletException {
+                            serviceJspFile(null, null, jspFile, null, true);
+                            return null;
+                        }
+                    });
+                } else {
+                    serviceJspFile(null, null, jspFile, null, true);
+                }
+            } catch (IOException e) {
+                throw new ServletException("Could not precompile jsp: " + jspFile, e);
+            } catch (PrivilegedActionException e) {
+                Throwable t = e.getCause();
+                if (t instanceof ServletException) throw (ServletException)t;
+                throw new ServletException("Could not precompile jsp: " + jspFile, e);
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug(Localizer.getMessage("jsp.message.scratch.dir.is",
+                    options.getScratchDir().toString()));
+            log.debug(Localizer.getMessage("jsp.message.dont.modify.servlets"));
+        }
+    }
+
+
+    /**
+     * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
+     * the number of JSPs that have been loaded into the webapp with which
+     * this JspServlet is associated.
+     *
+     * <p>This info may be used for monitoring purposes.
+     *
+     * @return The number of JSPs that have been loaded into the webapp with
+     * which this JspServlet is associated
+     */
+    public int getJspCount() {
+        return this.rctxt.getJspCount();
+    }
+
+
+    /**
+     * Resets the JSP reload counter.
+     *
+     * @param count Value to which to reset the JSP reload counter
+     */
+    public void setJspReloadCount(int count) {
+        this.rctxt.setJspReloadCount(count);
+    }
+
+
+    /**
+     * Gets the number of JSPs that have been reloaded.
+     *
+     * <p>This info may be used for monitoring purposes.
+     *
+     * @return The number of JSPs (in the webapp with which this JspServlet is
+     * associated) that have been reloaded
+     */
+    public int getJspReloadCount() {
+        return this.rctxt.getJspReloadCount();
+    }
+
+
+    /**
+     * Gets the number of JSPs that are in the JSP limiter queue
+     *
+     * <p>This info may be used for monitoring purposes.
+     *
+     * @return The number of JSPs (in the webapp with which this JspServlet is
+     * associated) that are in the JSP limiter queue
+     */
+    public int getJspQueueLength() {
+        return this.rctxt.getJspQueueLength();
+    }
+
+
+    /**
+     * Gets the number of JSPs that have been unloaded.
+     *
+     * <p>This info may be used for monitoring purposes.
+     *
+     * @return The number of JSPs (in the webapp with which this JspServlet is
+     * associated) that have been unloaded
+     */
+    public int getJspUnloadCount() {
+        return this.rctxt.getJspUnloadCount();
+    }
+
+
+    /**
+     * <p>Look for a <em>precompilation request</em> as described in
+     * Section 8.4.2 of the JSP 1.2 Specification.  <strong>WARNING</strong> -
+     * we cannot use <code>request.getParameter()</code> for this, because
+     * that will trigger parsing all of the request parameters, and not give
+     * a servlet the opportunity to call
+     * <code>request.setCharacterEncoding()</code> first.</p>
+     *
+     * @param request The servlet request we are processing
+     *
+     * @exception ServletException if an invalid parameter value for the
+     *  <code>jsp_precompile</code> parameter name is specified
+     */
+    boolean preCompile(HttpServletRequest request) throws ServletException {
+
+        String queryString = request.getQueryString();
+        if (queryString == null) {
+            return (false);
+        }
+        int start = queryString.indexOf(Constants.PRECOMPILE);
+        if (start < 0) {
+            return (false);
+        }
+        queryString =
+            queryString.substring(start + Constants.PRECOMPILE.length());
+        if (queryString.length() == 0) {
+            return (true);             // ?jsp_precompile
+        }
+        if (queryString.startsWith("&")) {
+            return (true);             // ?jsp_precompile&foo=bar...
+        }
+        if (!queryString.startsWith("=")) {
+            return (false);            // part of some other name or value
+        }
+        int limit = queryString.length();
+        int ampersand = queryString.indexOf("&");
+        if (ampersand > 0) {
+            limit = ampersand;
+        }
+        String value = queryString.substring(1, limit);
+        if (value.equals("true")) {
+            return (true);             // ?jsp_precompile=true
+        } else if (value.equals("false")) {
+            // Spec says if jsp_precompile=false, the request should not
+            // be delivered to the JSP page; the easiest way to implement
+            // this is to set the flag to true, and precompile the page anyway.
+            // This still conforms to the spec, since it says the
+            // precompilation request can be ignored.
+            return (true);             // ?jsp_precompile=false
+        } else {
+            throw new ServletException("Cannot have request parameter " +
+                                       Constants.PRECOMPILE + " set to " +
+                                       value);
+        }
+
+    }
+    
+
+    @Override
+    public void service (HttpServletRequest request, 
+                             HttpServletResponse response)
+                throws ServletException, IOException {
+        //jspFile may be configured as an init-param for this servlet instance
+        String jspUri = jspFile;
+
+        if (jspUri == null) {
+            // JSP specified via <jsp-file> in <servlet> declaration and supplied through
+            //custom servlet container code
+            jspUri = (String) request.getAttribute(Constants.JSP_FILE);
+        }
+        if (jspUri == null) {
+            /*
+             * Check to see if the requested JSP has been the target of a
+             * RequestDispatcher.include()
+             */
+            jspUri = (String) request.getAttribute(
+                    RequestDispatcher.INCLUDE_SERVLET_PATH);
+            if (jspUri != null) {
+                /*
+                 * Requested JSP has been target of
+                 * RequestDispatcher.include(). Its path is assembled from the
+                 * relevant javax.servlet.include.* request attributes
+                 */
+                String pathInfo = (String) request.getAttribute(
+                        RequestDispatcher.INCLUDE_PATH_INFO);
+                if (pathInfo != null) {
+                    jspUri += pathInfo;
+                }
+            } else {
+                /*
+                 * Requested JSP has not been the target of a 
+                 * RequestDispatcher.include(). Reconstruct its path from the
+                 * request's getServletPath() and getPathInfo()
+                 */
+                jspUri = request.getServletPath();
+                String pathInfo = request.getPathInfo();
+                if (pathInfo != null) {
+                    jspUri += pathInfo;
+                }
+            }
+        }
+
+        if (log.isDebugEnabled()) {    
+            log.debug("JspEngine --> " + jspUri);
+            log.debug("\t     ServletPath: " + request.getServletPath());
+            log.debug("\t        PathInfo: " + request.getPathInfo());
+            log.debug("\t        RealPath: " + context.getRealPath(jspUri));
+            log.debug("\t      RequestURI: " + request.getRequestURI());
+            log.debug("\t     QueryString: " + request.getQueryString());
+        }
+
+        try {
+            boolean precompile = preCompile(request);
+            serviceJspFile(request, response, jspUri, null, precompile);
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (ServletException e) {
+            throw e;
+        } catch (IOException e) {
+            throw e;
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            throw new ServletException(e);
+        }
+
+    }
+
+    @Override
+    public void destroy() {
+        if (log.isDebugEnabled()) {
+            log.debug("JspServlet.destroy()");
+        }
+
+        rctxt.destroy();
+    }
+
+
+    @Override
+    public void periodicEvent() {
+        rctxt.checkUnload();
+        rctxt.checkCompile();
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+    private void serviceJspFile(HttpServletRequest request,
+                                HttpServletResponse response, String jspUri,
+                                Throwable exception, boolean precompile)
+        throws ServletException, IOException {
+
+        JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
+        if (wrapper == null) {
+            synchronized(this) {
+                wrapper = rctxt.getWrapper(jspUri);
+                if (wrapper == null) {
+                    // Check if the requested JSP page exists, to avoid
+                    // creating unnecessary directories and files.
+                    if (null == context.getResource(jspUri)) {
+                        handleMissingResource(request, response, jspUri);
+                        return;
+                    }
+                    boolean isErrorPage = exception != null;
+                    wrapper = new JspServletWrapper(config, options, jspUri,
+                                                    isErrorPage, rctxt);
+                    rctxt.addWrapper(jspUri,wrapper);
+                }
+            }
+        }
+
+        try {
+            wrapper.service(request, response, precompile);
+        } catch (FileNotFoundException fnfe) {
+            handleMissingResource(request, response, jspUri);
+        }
+
+    }
+
+
+    private void handleMissingResource(HttpServletRequest request,
+            HttpServletResponse response, String jspUri)
+            throws ServletException, IOException {
+
+        String includeRequestUri =
+            (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
+
+        if (includeRequestUri != null) {
+            // This file was included. Throw an exception as
+            // a response.sendError() will be ignored
+            String msg =
+                Localizer.getMessage("jsp.error.file.not.found",jspUri);
+            // Strictly, filtering this is an application
+            // responsibility but just in case...
+            throw new ServletException(SecurityUtil.filter(msg));
+        } else {
+            try {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                        request.getRequestURI());
+            } catch (IllegalStateException ise) {
+                log.error(Localizer.getMessage("jsp.error.file.not.found",
+                        jspUri));
+            }
+        }
+        return;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspServletWrapper.java b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspServletWrapper.java
new file mode 100644
index 0000000..5061c40
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/JspServletWrapper.java
@@ -0,0 +1,574 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.tagext.TagInfo;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.apache.jasper.compiler.ErrorDispatcher;
+import org.apache.jasper.compiler.JarResource;
+import org.apache.jasper.compiler.JavacErrorDetail;
+import org.apache.jasper.compiler.JspRuntimeContext;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.jasper.runtime.InstanceManagerFactory;
+import org.apache.jasper.runtime.JspSourceDependent;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.jasper.util.FastRemovalDequeue;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.InstanceManager;
+
+/**
+ * The JSP engine (a.k.a Jasper).
+ *
+ * The servlet container is responsible for providing a
+ * URLClassLoader for the web application context Jasper
+ * is being used in. Jasper will try get the Tomcat
+ * ServletContext attribute for its ServletContext class
+ * loader, if that fails, it uses the parent class loader.
+ * In either case, it must be a URLClassLoader.
+ *
+ * @author Anil K. Vijendran
+ * @author Harish Prabandham
+ * @author Remy Maucherat
+ * @author Kin-man Chung
+ * @author Glenn Nielsen
+ * @author Tim Fennell
+ */
+
+@SuppressWarnings("deprecation") // Have to support SingleThreadModel
+public class JspServletWrapper {
+
+    // Logger
+    private final Log log = LogFactory.getLog(JspServletWrapper.class);
+
+    private Servlet theServlet;
+    private String jspUri;
+    private Class<?> tagHandlerClass;
+    private JspCompilationContext ctxt;
+    private long available = 0L;
+    private ServletConfig config;
+    private Options options;
+    private boolean firstTime = true;
+    /** Whether the servlet needs reloading on next access */
+    private volatile boolean reload = true;
+    private boolean isTagFile;
+    private int tripCount;
+    private JasperException compileException;
+    /** Timestamp of last time servlet resource was modified */
+    private volatile long servletClassLastModifiedTime;
+    private long lastModificationTest = 0L;
+    private long lastUsageTime = System.currentTimeMillis();
+    private FastRemovalDequeue<JspServletWrapper>.Entry unloadHandle;
+    private final boolean unloadAllowed;
+    private final boolean unloadByCount;
+    private final boolean unloadByIdle;
+
+    /*
+     * JspServletWrapper for JSP pages.
+     */
+    public JspServletWrapper(ServletConfig config, Options options, String jspUri,
+                      boolean isErrorPage, JspRuntimeContext rctxt) {
+
+        this.isTagFile = false;
+        this.config = config;
+        this.options = options;
+        this.jspUri = jspUri;
+        unloadByCount = options.getMaxLoadedJsps() > 0 ? true : false;
+        unloadByIdle = options.getJspIdleTimeout() > 0 ? true : false;
+        unloadAllowed = unloadByCount || unloadByIdle ? true : false;
+        ctxt = new JspCompilationContext(jspUri, isErrorPage, options,
+                                         config.getServletContext(),
+                                         this, rctxt);
+    }
+
+    /*
+     * JspServletWrapper for tag files.
+     */
+    public JspServletWrapper(ServletContext servletContext,
+                             Options options,
+                             String tagFilePath,
+                             TagInfo tagInfo,
+                             JspRuntimeContext rctxt,
+                             JarResource tagJarResource) {
+
+        this.isTagFile = true;
+        this.config = null;        // not used
+        this.options = options;
+        this.jspUri = tagFilePath;
+        this.tripCount = 0;
+        unloadByCount = options.getMaxLoadedJsps() > 0 ? true : false;
+        unloadByIdle = options.getJspIdleTimeout() > 0 ? true : false;
+        unloadAllowed = unloadByCount || unloadByIdle ? true : false;
+        ctxt = new JspCompilationContext(jspUri, tagInfo, options,
+                                         servletContext, this, rctxt,
+                                         tagJarResource);
+    }
+
+    public JspCompilationContext getJspEngineContext() {
+        return ctxt;
+    }
+
+    public void setReload(boolean reload) {
+        this.reload = reload;
+    }
+
+    public Servlet getServlet() throws ServletException {
+        // DCL on 'reload' requires that 'reload' be volatile
+        // (this also forces a read memory barrier, ensuring the 
+        // new servlet object is read consistently)
+        if (reload) {
+            synchronized (this) {
+                // Synchronizing on jsw enables simultaneous loading
+                // of different pages, but not the same page.
+                if (reload) {
+                    // This is to maintain the original protocol.
+                    destroy();
+                    
+                    final Servlet servlet;
+
+                    try {
+                        InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config);
+                        servlet = (Servlet) instanceManager.newInstance(ctxt.getFQCN(), ctxt.getJspLoader());
+                    } catch (IllegalAccessException e) {
+                        throw new JasperException(e);
+                    } catch (InstantiationException e) {
+                        throw new JasperException(e);
+                    } catch (Exception e) {
+                        throw new JasperException(e);
+                    }
+                    
+                    servlet.init(config);
+
+                    if (!firstTime) {
+                        ctxt.getRuntimeContext().incrementJspReloadCount();
+                    }
+
+                    theServlet = servlet;
+                    reload = false;
+                    // Volatile 'reload' forces in order write of 'theServlet' and new servlet object
+                }
+            }    
+        }
+        return theServlet;
+    }
+
+    public ServletContext getServletContext() {
+        return ctxt.getServletContext();
+    }
+
+    /**
+     * Sets the compilation exception for this JspServletWrapper.
+     *
+     * @param je The compilation exception
+     */
+    public void setCompilationException(JasperException je) {
+        this.compileException = je;
+    }
+
+    /**
+     * Sets the last-modified time of the servlet class file associated with
+     * this JspServletWrapper.
+     *
+     * @param lastModified Last-modified time of servlet class
+     */
+    public void setServletClassLastModifiedTime(long lastModified) {
+        // DCL requires servletClassLastModifiedTime be volatile
+        // to force read and write barriers on access/set
+        // (and to get atomic write of long)
+        if (this.servletClassLastModifiedTime < lastModified) {
+            synchronized (this) {
+                if (this.servletClassLastModifiedTime < lastModified) {
+                    this.servletClassLastModifiedTime = lastModified;
+                    reload = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Compile (if needed) and load a tag file
+     */
+    public Class<?> loadTagFile() throws JasperException {
+
+        try {
+            if (ctxt.isRemoved()) {
+                throw new FileNotFoundException(jspUri);
+            }
+            if (options.getDevelopment() || firstTime ) {
+                synchronized (this) {
+                    firstTime = false;
+                    ctxt.compile();
+                }
+            } else {
+                if (compileException != null) {
+                    throw compileException;
+                }
+            }
+
+            if (reload) {
+                tagHandlerClass = ctxt.load();
+                reload = false;
+            }
+        } catch (FileNotFoundException ex) {
+            throw new JasperException(ex);
+        }
+
+        return tagHandlerClass;
+    }
+
+    /**
+     * Compile and load a prototype for the Tag file.  This is needed
+     * when compiling tag files with circular dependencies.  A prototype
+     * (skeleton) with no dependencies on other other tag files is
+     * generated and compiled.
+     */
+    public Class<?> loadTagFilePrototype() throws JasperException {
+
+        ctxt.setPrototypeMode(true);
+        try {
+            return loadTagFile();
+        } finally {
+            ctxt.setPrototypeMode(false);
+        }
+    }
+
+    /**
+     * Get a list of files that the current page has source dependency on.
+     */
+    public java.util.List<String> getDependants() {
+        try {
+            Object target;
+            if (isTagFile) {
+                if (reload) {
+                    tagHandlerClass = ctxt.load();
+                    reload = false;
+                }
+                target = tagHandlerClass.newInstance();
+            } else {
+                target = getServlet();
+            }
+            if (target != null && target instanceof JspSourceDependent) {
+                return ((JspSourceDependent) target).getDependants();
+            }
+        } catch (Throwable ex) {
+            ExceptionUtils.handleThrowable(ex);
+        }
+        return null;
+    }
+
+    public boolean isTagFile() {
+        return this.isTagFile;
+    }
+
+    public int incTripCount() {
+        return tripCount++;
+    }
+
+    public int decTripCount() {
+        return tripCount--;
+    }
+
+    public String getJspUri() {
+        return jspUri;
+    }
+
+    public FastRemovalDequeue<JspServletWrapper>.Entry getUnloadHandle() {
+        return unloadHandle;
+    }
+
+    public void service(HttpServletRequest request, 
+                        HttpServletResponse response,
+                        boolean precompile)
+            throws ServletException, IOException, FileNotFoundException {
+        
+        Servlet servlet;
+
+        try {
+
+            if (ctxt.isRemoved()) {
+                throw new FileNotFoundException(jspUri);
+            }
+
+            if ((available > 0L) && (available < Long.MAX_VALUE)) {
+                if (available > System.currentTimeMillis()) {
+                    response.setDateHeader("Retry-After", available);
+                    response.sendError
+                        (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+                         Localizer.getMessage("jsp.error.unavailable"));
+                    return;
+                }
+
+                // Wait period has expired. Reset.
+                available = 0;
+            }
+
+            /*
+             * (1) Compile
+             */
+            if (options.getDevelopment() || firstTime ) {
+                synchronized (this) {
+                    firstTime = false;
+
+                    // The following sets reload to true, if necessary
+                    ctxt.compile();
+                }
+            } else {
+                if (compileException != null) {
+                    // Throw cached compilation exception
+                    throw compileException;
+                }
+            }
+
+            /*
+             * (2) (Re)load servlet class file
+             */
+            servlet = getServlet();
+
+            // If a page is to be precompiled only, return.
+            if (precompile) {
+                return;
+            }
+
+        } catch (ServletException ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw ex;
+        } catch (FileNotFoundException fnfe) {
+            // File has been removed. Let caller handle this.
+            throw fnfe;
+        } catch (IOException ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw ex;
+        } catch (IllegalStateException ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw ex;
+        } catch (Exception ex) {
+            if (options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw new JasperException(ex);
+        }
+
+        try {
+            
+            /*
+             * (3) Handle limitation of number of loaded Jsps
+             */
+            if (unloadAllowed) {
+                synchronized(this) {
+                    if (unloadByCount) {
+                        if (unloadHandle == null) {
+                            unloadHandle = ctxt.getRuntimeContext().push(this);
+                        } else if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) {
+                            ctxt.getRuntimeContext().makeYoungest(unloadHandle);
+                            lastUsageTime = System.currentTimeMillis();
+                        }
+                    } else {
+                        if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) {
+                            lastUsageTime = System.currentTimeMillis();
+                        }
+                    }
+                }
+            }
+            /*
+             * (4) Service request
+             */
+            if (servlet instanceof SingleThreadModel) {
+               // sync on the wrapper so that the freshness
+               // of the page is determined right before servicing
+               synchronized (this) {
+                   servlet.service(request, response);
+                }
+            } else {
+                servlet.service(request, response);
+            }
+        } catch (UnavailableException ex) {
+            String includeRequestUri = (String)
+                request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
+            if (includeRequestUri != null) {
+                // This file was included. Throw an exception as
+                // a response.sendError() will be ignored by the
+                // servlet engine.
+                throw ex;
+            }
+
+            int unavailableSeconds = ex.getUnavailableSeconds();
+            if (unavailableSeconds <= 0) {
+                unavailableSeconds = 60;        // Arbitrary default
+            }
+            available = System.currentTimeMillis() +
+                (unavailableSeconds * 1000L);
+            response.sendError
+                (HttpServletResponse.SC_SERVICE_UNAVAILABLE, 
+                 ex.getMessage());
+        } catch (ServletException ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw ex;
+        } catch (IOException ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw ex;
+        } catch (IllegalStateException ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw ex;
+        } catch (Exception ex) {
+            if(options.getDevelopment()) {
+                throw handleJspException(ex);
+            }
+            throw new JasperException(ex);
+        }
+    }
+
+    public void destroy() {
+        if (theServlet != null) {
+            theServlet.destroy();
+            InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config);
+            try {
+                instanceManager.destroyInstance(theServlet);
+            } catch (Exception e) {
+                // Log any exception, since it can't be passed along
+                log.error(Localizer.getMessage("jsp.error.file.not.found",
+                        e.getMessage()), e);
+            }
+        }
+    }
+
+    /**
+     * @return Returns the lastModificationTest.
+     */
+    public long getLastModificationTest() {
+        return lastModificationTest;
+    }
+    /**
+     * @param lastModificationTest The lastModificationTest to set.
+     */
+    public void setLastModificationTest(long lastModificationTest) {
+        this.lastModificationTest = lastModificationTest;
+    }
+
+    /**
+     * @return the lastUsageTime.
+     */
+    public long getLastUsageTime() {
+        return lastUsageTime;
+    }
+
+    /**
+     * <p>Attempts to construct a JasperException that contains helpful information
+     * about what went wrong. Uses the JSP compiler system to translate the line
+     * number in the generated servlet that originated the exception to a line
+     * number in the JSP.  Then constructs an exception containing that
+     * information, and a snippet of the JSP to help debugging.
+     * Please see http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 and
+     * http://www.tfenne.com/jasper/ for more details.
+     *</p>
+     *
+     * @param ex the exception that was the cause of the problem.
+     * @return a JasperException with more detailed information
+     */
+    protected JasperException handleJspException(Exception ex) {
+        try {
+            Throwable realException = ex;
+            if (ex instanceof ServletException) {
+                realException = ((ServletException) ex).getRootCause();
+            }
+
+            // First identify the stack frame in the trace that represents the JSP
+            StackTraceElement[] frames = realException.getStackTrace();
+            StackTraceElement jspFrame = null;
+
+            for (int i=0; i<frames.length; ++i) {
+                if ( frames[i].getClassName().equals(this.getServlet().getClass().getName()) ) {
+                    jspFrame = frames[i];
+                    break;
+                }
+            }
+
+            
+            if (jspFrame == null ||
+                    this.ctxt.getCompiler().getPageNodes() == null) {
+                // If we couldn't find a frame in the stack trace corresponding
+                // to the generated servlet class or we don't have a copy of the
+                // parsed JSP to hand, we can't really add anything
+                return new JasperException(ex);
+            }
+
+            int javaLineNumber = jspFrame.getLineNumber();
+            JavacErrorDetail detail = ErrorDispatcher.createJavacError(
+                    jspFrame.getMethodName(),
+                    this.ctxt.getCompiler().getPageNodes(),
+                    null,
+                    javaLineNumber,
+                    ctxt);
+
+            // If the line number is less than one we couldn't find out
+            // where in the JSP things went wrong
+            int jspLineNumber = detail.getJspBeginLineNumber();
+            if (jspLineNumber < 1) {
+                throw new JasperException(ex);
+            }
+
+            if (options.getDisplaySourceFragment()) {
+                return new JasperException(Localizer.getMessage
+                        ("jsp.exception", detail.getJspFileName(),
+                                "" + jspLineNumber) + Constants.NEWLINE +
+                                Constants.NEWLINE + detail.getJspExtract() +
+                                Constants.NEWLINE + Constants.NEWLINE + 
+                                "Stacktrace:", ex);
+                
+            }
+
+            return new JasperException(Localizer.getMessage
+                    ("jsp.exception", detail.getJspFileName(),
+                            "" + jspLineNumber), ex);
+        } catch (Exception je) {
+            // If anything goes wrong, just revert to the original behaviour
+            if (ex instanceof JasperException) {
+                return (JasperException) ex;
+            }
+            return new JasperException(ex);
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/mbeans-descriptors.xml
new file mode 100644
index 0000000..5c6aafc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/servlet/mbeans-descriptors.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<mbeans-descriptors>
+
+  <mbean         name="JspMonitor"
+          description="JSP Monitoring"
+               domain="Catalina"
+                group="Monitoring"
+                 type="org.apache.jasper.servlet.JspServlet">
+
+    <attribute   name="jspCount"
+          description="The number of JSPs that have been loaded into a webapp"
+                 type="int"
+                 writeable="false"/>
+
+    <attribute   name="jspReloadCount"
+          description="The number of JSPs that have been reloaded"
+                 type="int"/>
+
+    <attribute   name="jspUnloadCount"
+          description="The number of JSPs that have been unloaded"
+                 type="int"/>
+
+    <attribute   name="jspQueueLength"
+          description="The length of the JSP queue (if enabled via maxLoadedJsps)"
+                 type="int"/>
+
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/Util.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/Util.java
new file mode 100644
index 0000000..ca90ed1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/Util.java
@@ -0,0 +1,336 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.PageContext;
+
+import org.apache.jasper.Constants;
+
+/**
+ * Util contains some often used consts, static methods and embedded class
+ * to support the JSTL tag plugin.
+ */
+
+public class Util {
+    
+    public static final String VALID_SCHEME_CHAR = 
+        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-";
+    
+    public static final String DEFAULT_ENCODING = 
+        "ISO-8859-1";
+    
+    public static final int HIGHEST_SPECIAL = '>';
+    
+    private static char[][] specialCharactersRepresentation = new char[HIGHEST_SPECIAL + 1][];
+    
+    static {
+        specialCharactersRepresentation['&'] = "&amp;".toCharArray();
+        specialCharactersRepresentation['<'] = "&lt;".toCharArray();
+        specialCharactersRepresentation['>'] = "&gt;".toCharArray();
+        specialCharactersRepresentation['"'] = "&#034;".toCharArray();
+        specialCharactersRepresentation['\''] = "&#039;".toCharArray();
+    }
+    
+    /**
+     * Converts the given string description of a scope to the corresponding
+     * PageContext constant.
+     *
+     * The validity of the given scope has already been checked by the
+     * appropriate TLV.
+     *
+     * @param scope String description of scope
+     *
+     * @return PageContext constant corresponding to given scope description
+     * 
+     * taken from org.apache.taglibs.standard.tag.common.core.Util  
+     */
+    public static int getScope(String scope){
+        int ret = PageContext.PAGE_SCOPE;
+        
+        if("request".equalsIgnoreCase(scope)){
+            ret = PageContext.REQUEST_SCOPE;
+        }else if("session".equalsIgnoreCase(scope)){
+            ret = PageContext.SESSION_SCOPE;
+        }else if("application".equalsIgnoreCase(scope)){
+            ret = PageContext.APPLICATION_SCOPE;
+        }
+        
+        return ret;
+    }
+    
+    /**
+     * Returns <tt>true</tt> if our current URL is absolute,
+     * <tt>false</tt> otherwise.
+     * taken from org.apache.taglibs.standard.tag.common.core.ImportSupport
+     */
+    public static boolean isAbsoluteUrl(String url){
+        if(url == null){
+            return false;
+        }
+        
+        int colonPos = url.indexOf(":");
+        if(colonPos == -1){
+            return false;
+        }
+        
+        for(int i=0;i<colonPos;i++){
+            if(VALID_SCHEME_CHAR.indexOf(url.charAt(i)) == -1){
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Get the value associated with a content-type attribute.
+     * Syntax defined in RFC 2045, section 5.1.
+     * taken from org.apache.taglibs.standard.tag.common.core.Util
+     */
+    public static String getContentTypeAttribute(String input, String name) {
+        int begin;
+        int end;
+        int index = input.toUpperCase(Locale.ENGLISH).indexOf(name.toUpperCase(Locale.ENGLISH));
+        if (index == -1) return null;
+        index = index + name.length(); // positioned after the attribute name
+        index = input.indexOf('=', index); // positioned at the '='
+        if (index == -1) return null;
+        index += 1; // positioned after the '='
+        input = input.substring(index).trim();
+        
+        if (input.charAt(0) == '"') {
+            // attribute value is a quoted string
+            begin = 1;
+            end = input.indexOf('"', begin);
+            if (end == -1) return null;
+        } else {
+            begin = 0;
+            end = input.indexOf(';');
+            if (end == -1) end = input.indexOf(' ');
+            if (end == -1) end = input.length();
+        }
+        return input.substring(begin, end).trim();
+    }
+    
+    /**
+     * Strips a servlet session ID from <tt>url</tt>.  The session ID
+     * is encoded as a URL "path parameter" beginning with "jsessionid=".
+     * We thus remove anything we find between ";jsessionid=" (inclusive)
+     * and either EOS or a subsequent ';' (exclusive).
+     * 
+     * taken from org.apache.taglibs.standard.tag.common.core.ImportSupport
+     */
+    public static String stripSession(String url) {
+        StringBuilder u = new StringBuilder(url);
+        int sessionStart;
+        while ((sessionStart = u.toString().indexOf(";" + Constants.SESSION_PARAMETER_NAME + "=")) != -1) {
+            int sessionEnd = u.toString().indexOf(";", sessionStart + 1);
+            if (sessionEnd == -1)
+                sessionEnd = u.toString().indexOf("?", sessionStart + 1);
+            if (sessionEnd == -1) // still
+                sessionEnd = u.length();
+            u.delete(sessionStart, sessionEnd);
+        }
+        return u.toString();
+    }
+    
+    
+    /**
+     * Performs the following substring replacements
+     * (to facilitate output to XML/HTML pages):
+     *
+     *    & -> &amp;
+     *    < -> &lt;
+     *    > -> &gt;
+     *    " -> &#034;
+     *    ' -> &#039;
+     *
+     * See also OutSupport.writeEscapedXml().
+     * 
+     * taken from org.apache.taglibs.standard.tag.common.core.Util
+     */
+    @SuppressWarnings("null") // escapedBuffer cannot be null
+    public static String escapeXml(String buffer) {
+        int start = 0;
+        int length = buffer.length();
+        char[] arrayBuffer = buffer.toCharArray();
+        StringBuilder escapedBuffer = null;
+        
+        for (int i = 0; i < length; i++) {
+            char c = arrayBuffer[i];
+            if (c <= HIGHEST_SPECIAL) {
+                char[] escaped = specialCharactersRepresentation[c];
+                if (escaped != null) {
+                    // create StringBuilder to hold escaped xml string
+                    if (start == 0) {
+                        escapedBuffer = new StringBuilder(length + 5);
+                    }
+                    // add unescaped portion
+                    if (start < i) {
+                        escapedBuffer.append(arrayBuffer,start,i-start);
+                    }
+                    start = i + 1;
+                    // add escaped xml
+                    escapedBuffer.append(escaped);
+                }
+            }
+        }
+        // no xml escaping was necessary
+        if (start == 0) {
+            return buffer;
+        }
+        // add rest of unescaped portion
+        if (start < length) {
+            escapedBuffer.append(arrayBuffer,start,length-start);
+        }
+        return escapedBuffer.toString();
+    }
+    
+    /** Utility methods
+     * taken from org.apache.taglibs.standard.tag.common.core.UrlSupport
+     */
+    public static String resolveUrl(
+            String url, String context, PageContext pageContext)
+    throws JspException {
+        // don't touch absolute URLs
+        if (isAbsoluteUrl(url))
+            return url;
+        
+        // normalize relative URLs against a context root
+        HttpServletRequest request =
+            (HttpServletRequest) pageContext.getRequest();
+        if (context == null) {
+            if (url.startsWith("/"))
+                return (request.getContextPath() + url);
+            else
+                return url;
+        } else {
+            if (!context.startsWith("/") || !url.startsWith("/")) {
+                throw new JspTagException(
+                "In URL tags, when the \"context\" attribute is specified, values of both \"context\" and \"url\" must start with \"/\".");
+            }
+            if (context.equals("/")) {
+                // Don't produce string starting with '//', many
+                // browsers interpret this as host name, not as
+                // path on same host.
+                return url;
+            } else {
+                return (context + url);
+            }
+        }
+    }
+    
+    /** Wraps responses to allow us to retrieve results as Strings. 
+     * mainly taken from org.apache.taglibs.standard.tag.common.core.importSupport 
+     */
+    public static class ImportResponseWrapper extends HttpServletResponseWrapper{
+        
+        private StringWriter sw = new StringWriter();
+        private ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        private ServletOutputStream sos = new ServletOutputStream() {
+            @Override
+            public void write(int b) throws IOException {
+                bos.write(b);
+            }
+        };
+        private boolean isWriterUsed;
+        private boolean isStreamUsed;
+        private int status = 200;
+        private String charEncoding;
+        
+        public ImportResponseWrapper(HttpServletResponse arg0) {
+            super(arg0);
+            // TODO Auto-generated constructor stub
+        }
+        
+        @Override
+        public PrintWriter getWriter() {
+            if (isStreamUsed)
+                throw new IllegalStateException("Unexpected internal error during &lt;import&gt: " +
+                "Target servlet called getWriter(), then getOutputStream()");
+            isWriterUsed = true;
+            return new PrintWriter(sw);
+        }
+        
+        @Override
+        public ServletOutputStream getOutputStream() {
+            if (isWriterUsed)
+                throw new IllegalStateException("Unexpected internal error during &lt;import&gt: " +
+                "Target servlet called getOutputStream(), then getWriter()");
+            isStreamUsed = true;
+            return sos;
+        }
+        
+        /** Has no effect. */
+        @Override
+        public void setContentType(String x) {
+            // ignore
+        }
+        
+        /** Has no effect. */
+        @Override
+        public void setLocale(Locale x) {
+            // ignore
+        }
+        
+        @Override
+        public void setStatus(int status) {
+            this.status = status;
+        }
+        
+        @Override
+        public int getStatus() {
+            return status;
+        }
+        
+        public String getCharEncoding(){
+            return this.charEncoding;
+        }
+        
+        public void setCharEncoding(String ce){
+            this.charEncoding = ce;
+        }
+        
+        public String getString() throws UnsupportedEncodingException {
+            if (isWriterUsed)
+                return sw.toString();
+            else if (isStreamUsed) {
+                if (this.charEncoding != null && !this.charEncoding.equals(""))
+                    return bos.toString(charEncoding);
+                else
+                    return bos.toString("ISO-8859-1");
+            } else
+                return ""; // target didn't write anything
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Catch.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Catch.java
new file mode 100644
index 0000000..7e00652
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Catch.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class Catch implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        //flag for the existence of the var attribute
+        boolean hasVar = ctxt.isAttributeSpecified("var");
+        
+        //temp name for exception and caught
+        String exceptionName = ctxt.getTemporaryVariableName();
+        String caughtName = ctxt.getTemporaryVariableName();
+        
+        //main part to generate code
+        ctxt.generateJavaSource("boolean " + caughtName + " = false;");
+        ctxt.generateJavaSource("try{");
+        ctxt.generateBody();
+        ctxt.generateJavaSource("}");
+        
+        //do catch
+        ctxt.generateJavaSource("catch(Throwable " + exceptionName + "){");
+        
+        //if the var specified, the exception object should 
+        //be set to the attribute "var" defines in page scope 
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("    pageContext.setAttribute(\"" + strVar + "\", " 
+                    + exceptionName + ", PageContext.PAGE_SCOPE);");
+        }
+        
+        //whenever there's exception caught, 
+        //the flag caught should be set true;
+        ctxt.generateJavaSource("    " + caughtName + " = true;");
+        ctxt.generateJavaSource("}");
+        
+        //do finally
+        ctxt.generateJavaSource("finally{");
+        
+        //if var specified, the attribute it defines 
+        //in page scope should be removed
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("    if(!" + caughtName + "){");
+            ctxt.generateJavaSource("        pageContext.removeAttribute(\"" + strVar + "\", PageContext.PAGE_SCOPE);");
+            ctxt.generateJavaSource("    }");
+        }
+        
+        ctxt.generateJavaSource("}");
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Choose.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Choose.java
new file mode 100644
index 0000000..97eca05
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Choose.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public final class Choose implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        // Not much to do here, much of the work will be done in the
+        // containing tags, <c:when> and <c:otherwise>.
+        
+        ctxt.generateBody();
+        // See comments in When.java for the reason "}" is generated here.
+        ctxt.generateJavaSource("}");
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/ForEach.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/ForEach.java
new file mode 100644
index 0000000..2b8c00f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/ForEach.java
@@ -0,0 +1,346 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public final class ForEach implements TagPlugin {
+    
+    private boolean hasVar, hasBegin, hasEnd, hasStep;
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        String index = null;
+        
+        boolean hasVarStatus = ctxt.isAttributeSpecified("varStatus");
+        if (hasVarStatus) {
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasBegin = ctxt.isAttributeSpecified("begin");
+        hasEnd = ctxt.isAttributeSpecified("end");
+        hasStep = ctxt.isAttributeSpecified("step");
+        
+        boolean hasItems = ctxt.isAttributeSpecified("items");
+        if (hasItems) {
+            doCollection(ctxt);
+            return;
+        }
+        
+        // We must have a begin and end attributes
+        index = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("for (int " + index + " = ");
+        ctxt.generateAttribute("begin");
+        ctxt.generateJavaSource("; " + index + " <= ");
+        ctxt.generateAttribute("end");
+        if (hasStep) {
+            ctxt.generateJavaSource("; " + index + "+=");
+            ctxt.generateAttribute("step");
+            ctxt.generateJavaSource(") {");
+        }
+        else {
+            ctxt.generateJavaSource("; " + index + "++) {");
+        }
+        
+        // If var is specified and the body contains an EL, then sycn
+        // the var attribute
+        if (hasVar /* && ctxt.hasEL() */) {
+            ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
+            ctxt.generateAttribute("var");
+            ctxt.generateJavaSource(", String.valueOf(" + index + "));");
+        }
+        ctxt.generateBody();
+        ctxt.generateJavaSource("}");
+    }
+    
+    /**
+     * Generate codes for Collections
+     * The pseudo code is:
+     */
+    private void doCollection(TagPluginContext ctxt) {
+        
+        ctxt.generateImport("java.util.*");
+        generateIterators(ctxt);
+        
+        String itemsV = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("Object " + itemsV + "= ");
+        ctxt.generateAttribute("items");
+        ctxt.generateJavaSource(";");
+        
+        String indexV=null, beginV=null, endV=null, stepV=null;
+        if (hasBegin) {
+            beginV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + beginV + " = ");
+            ctxt.generateAttribute("begin");
+            ctxt.generateJavaSource(";");
+        }
+        if (hasEnd) {
+            indexV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + indexV + " = 0;");
+            endV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + endV + " = ");
+            ctxt.generateAttribute("end");
+            ctxt.generateJavaSource(";");
+        }
+        if (hasStep) {
+            stepV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("int " + stepV + " = ");
+            ctxt.generateAttribute("step");
+            ctxt.generateJavaSource(";");
+        }
+        
+        String iterV = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("Iterator " + iterV + " = null;");
+        // Object[]
+        ctxt.generateJavaSource("if (" + itemsV + " instanceof Object[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((Object[])" + itemsV + ");");
+        // boolean[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof boolean[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((boolean[])" + itemsV + ");");
+        // byte[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof byte[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((byte[])" + itemsV + ");");
+        // char[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof char[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((char[])" + itemsV + ");");
+        // short[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof short[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((short[])" + itemsV + ");");
+        // int[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof int[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((int[])" + itemsV + ");");
+        // long[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof long[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((long[])" + itemsV + ");");
+        // float[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof float[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((float[])" + itemsV + ");");
+        // double[]
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof double[])");
+        ctxt.generateJavaSource(iterV + "=toIterator((double[])" + itemsV + ");");
+        
+        // Collection
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Collection)");
+        ctxt.generateJavaSource(iterV + "=((Collection)" + itemsV + ").iterator();");
+        
+        // Iterator
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Iterator)");
+        ctxt.generateJavaSource(iterV + "=(Iterator)" + itemsV + ";");
+        
+        // Enumeration
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Enumeration)");
+        ctxt.generateJavaSource(iterV + "=toIterator((Enumeration)" + itemsV + ");");
+        
+        // Map
+        ctxt.generateJavaSource("else if (" + itemsV + " instanceof Map)");
+        ctxt.generateJavaSource(iterV + "=((Map)" + itemsV + ").entrySet().iterator();");
+        
+        if (hasBegin) {
+            String tV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("for (int " + tV + "=" + beginV + ";" +
+                    tV + ">0 && " + iterV + ".hasNext(); " +
+                    tV + "--)");
+            ctxt.generateJavaSource(iterV + ".next();");
+        }
+        
+        ctxt.generateJavaSource("while (" + iterV + ".hasNext()){");
+        if (hasVar) {
+            ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
+            ctxt.generateAttribute("var");
+            ctxt.generateJavaSource(", " + iterV + ".next());");
+        }
+        
+        ctxt.generateBody();
+        
+        if (hasStep) {
+            String tV = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("for (int " + tV + "=" + stepV + "-1;" +
+                    tV + ">0 && " + iterV + ".hasNext(); " +
+                    tV + "--)");
+            ctxt.generateJavaSource(iterV + ".next();");
+        }
+        if (hasEnd) {
+            if (hasStep) {
+                ctxt.generateJavaSource(indexV + "+=" + stepV + ";");
+            }
+            else {
+                ctxt.generateJavaSource(indexV + "++;");
+            }
+            if (hasBegin) {
+                ctxt.generateJavaSource("if(" + beginV + "+" + indexV +
+                        ">"+ endV + ")");
+            }
+            else {
+                ctxt.generateJavaSource("if(" + indexV + ">" + endV + ")");
+            }
+            ctxt.generateJavaSource("break;");
+        }
+        ctxt.generateJavaSource("}"); // while
+    }
+    
+    /**
+     * Generate iterators for data types supported in items
+     */
+    private void generateIterators(TagPluginContext ctxt) {
+        
+        // Object[]
+        ctxt.generateDeclaration("ObjectArrayIterator", 
+                "private Iterator toIterator(final Object[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return a[index++];}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // boolean[]
+        ctxt.generateDeclaration("booleanArrayIterator", 
+                "private Iterator toIterator(final boolean[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Boolean(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // byte[]
+        ctxt.generateDeclaration("byteArrayIterator", 
+                "private Iterator toIterator(final byte[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Byte(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // char[]
+        ctxt.generateDeclaration("charArrayIterator", 
+                "private Iterator toIterator(final char[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Character(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // short[]
+        ctxt.generateDeclaration("shortArrayIterator", 
+                "private Iterator toIterator(final short[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Short(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // int[]
+        ctxt.generateDeclaration("intArrayIterator", 
+                "private Iterator toIterator(final int[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Integer(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // long[]
+        ctxt.generateDeclaration("longArrayIterator", 
+                "private Iterator toIterator(final long[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Long(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // float[]
+        ctxt.generateDeclaration("floatArrayIterator",
+                "private Iterator toIterator(final float[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Float(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // double[]
+        ctxt.generateDeclaration("doubleArrayIterator",
+                "private Iterator toIterator(final double[] a){\n" +
+                "  return (new Iterator() {\n" +
+                "    int index=0;\n" +
+                "    public boolean hasNext() {\n" +
+                "      return index < a.length;}\n" +
+                "    public Object next() {\n" +
+                "      return new Double(a[index++]);}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+        // Enumeration
+        ctxt.generateDeclaration("enumIterator",
+                "private Iterator toIterator(final Enumeration e){\n" +
+                "  return (new Iterator() {\n" +
+                "    public boolean hasNext() {\n" +
+                "      return e.hasMoreElements();}\n" +
+                "    public Object next() {\n" +
+                "      return e.nextElement();}\n" +
+                "    public void remove() {}\n" +
+                "  });\n" +
+                "}"
+        );
+        
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/ForTokens.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/ForTokens.java
new file mode 100644
index 0000000..99d31da
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/ForTokens.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class ForTokens implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        boolean hasVar, hasVarStatus, hasBegin, hasEnd, hasStep;
+        
+        //init the flags
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasVarStatus = ctxt.isAttributeSpecified("varStatus");
+        hasBegin = ctxt.isAttributeSpecified("begin");
+        hasEnd = ctxt.isAttributeSpecified("end");
+        hasStep = ctxt.isAttributeSpecified("step");
+        
+        if(hasVarStatus){
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        //define all the temp variables' names
+        String itemsName = ctxt.getTemporaryVariableName();
+        String delimsName = ctxt.getTemporaryVariableName();
+        String stName = ctxt.getTemporaryVariableName();
+        String beginName = ctxt.getTemporaryVariableName();
+        String endName  = ctxt.getTemporaryVariableName();
+        String stepName = ctxt.getTemporaryVariableName();
+        String index = ctxt.getTemporaryVariableName();
+        String temp  = ctxt.getTemporaryVariableName();
+        String tokensCountName = ctxt.getTemporaryVariableName();
+        
+        //get the value of the "items" attribute 
+        ctxt.generateJavaSource("String " + itemsName + " = (String)");
+        ctxt.generateAttribute("items");
+        ctxt.generateJavaSource(";");
+        
+        //get the value of the "delim" attribute
+        ctxt.generateJavaSource("String " + delimsName + " = (String)");
+        ctxt.generateAttribute("delims");
+        ctxt.generateJavaSource(";");
+        
+        //new a StringTokenizer Object according to the "items" and the "delim"
+        ctxt.generateJavaSource("java.util.StringTokenizer " + stName + " = " +
+                "new java.util.StringTokenizer(" + itemsName + ", " + delimsName + ");");
+        
+        //if "begin" specified, move the token to the "begin" place
+        //and record the begin index. default begin place is 0.
+        ctxt.generateJavaSource("int " + tokensCountName + " = " + stName + ".countTokens();");
+        if(hasBegin){
+            ctxt.generateJavaSource("int " + beginName + " = "  );
+            ctxt.generateAttribute("begin");
+            ctxt.generateJavaSource(";");
+            ctxt.generateJavaSource("for(int " + index + " = 0; " + index + " < " + beginName + " && " + stName + ".hasMoreTokens(); " + index + "++, " + stName + ".nextToken()){}");
+        }else{
+            ctxt.generateJavaSource("int " + beginName + " = 0;");
+        }
+        
+        //when "end" is specified, if the "end" is more than the last index,
+        //record the end place as the last index, otherwise, record it as "end";
+        //default end place is the last index 
+        if(hasEnd){
+            ctxt.generateJavaSource("int " + endName + " = 0;"  );
+            ctxt.generateJavaSource("if((" + tokensCountName + " - 1) < ");
+            ctxt.generateAttribute("end");
+            ctxt.generateJavaSource("){");
+            ctxt.generateJavaSource("    " + endName + " = " + tokensCountName + " - 1;");
+            ctxt.generateJavaSource("}else{");
+            ctxt.generateJavaSource("    " + endName + " = ");
+            ctxt.generateAttribute("end");
+            ctxt.generateJavaSource(";}");
+        }else{
+            ctxt.generateJavaSource("int " + endName + " = " + tokensCountName + " - 1;");
+        }
+        
+        //get the step value from "step" if specified.
+        //default step value is 1.
+        if(hasStep){
+            ctxt.generateJavaSource("int " + stepName + " = "  );
+            ctxt.generateAttribute("step");
+            ctxt.generateJavaSource(";");
+        }else{
+            ctxt.generateJavaSource("int " + stepName + " = 1;");
+        }
+        
+        //the loop
+        ctxt.generateJavaSource("for(int " + index + " = " + beginName + "; " + index + " <= " + endName + "; " + index + "++){");
+        ctxt.generateJavaSource("    String " + temp + " = " + stName + ".nextToken();");
+        ctxt.generateJavaSource("    if(((" + index + " - " + beginName + ") % " + stepName + ") == 0){");
+        //if var specified, put the current token into the attribute "var" defines.
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("        pageContext.setAttribute(\"" + strVar + "\", " + temp + ");");
+        }
+        ctxt.generateBody();
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("}");
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/If.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/If.java
new file mode 100644
index 0000000..662c740
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/If.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public final class If implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        String condV = ctxt.getTemporaryVariableName();
+        ctxt.generateJavaSource("boolean " + condV + "=");
+        ctxt.generateAttribute("test");
+        ctxt.generateJavaSource(";");
+        if (ctxt.isAttributeSpecified("var")) {
+            String scope = "PageContext.PAGE_SCOPE";
+            if (ctxt.isAttributeSpecified("scope")) {
+                String scopeStr = ctxt.getConstantAttribute("scope");
+                if ("request".equals(scopeStr)) {
+                    scope = "PageContext.REQUEST_SCOPE";
+                } else if ("session".equals(scopeStr)) {
+                    scope = "PageContext.SESSION_SCOPE";
+                } else if ("application".equals(scopeStr)) {
+                    scope = "PageContext.APPLICATION_SCOPE";
+                }
+            }
+            ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
+            ctxt.generateAttribute("var");
+            ctxt.generateJavaSource(", new Boolean(" + condV + ")," + scope + ");");
+        }
+        ctxt.generateJavaSource("if (" + condV + "){");
+        ctxt.generateBody();
+        ctxt.generateJavaSource("}");
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Import.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Import.java
new file mode 100644
index 0000000..2090019
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Import.java
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Import implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        boolean hasContext, hasVar, hasScope, hasVarReader, hasCharEncoding;
+        
+        //flags
+        hasContext  = ctxt.isAttributeSpecified("context");
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasScope = ctxt.isAttributeSpecified("scope");
+        hasVarReader = ctxt.isAttributeSpecified("varReader");
+        hasCharEncoding = ctxt.isAttributeSpecified("charEncoding");
+        
+        //variables' names
+        String urlName = ctxt.getTemporaryVariableName();           
+        String contextName = ctxt.getTemporaryVariableName();       
+        String iauName = ctxt.getTemporaryVariableName();           // is absolute url
+        String urlObjName = ctxt.getTemporaryVariableName();        //URL object
+        String ucName = ctxt.getTemporaryVariableName();            //URLConnection
+        String inputStreamName = ctxt.getTemporaryVariableName();   
+        String tempReaderName = ctxt.getTemporaryVariableName();
+        String tempReaderName2 = ctxt.getTemporaryVariableName();
+        String charSetName = ctxt.getTemporaryVariableName();
+        String charEncodingName = ctxt.getTemporaryVariableName();
+        String contentTypeName = ctxt.getTemporaryVariableName();
+        String varReaderName = ctxt.getTemporaryVariableName();
+        String servletContextName = ctxt.getTemporaryVariableName();
+        String servletPathName = ctxt.getTemporaryVariableName();
+        String requestDispatcherName = ctxt.getTemporaryVariableName();
+        String irwName = ctxt.getTemporaryVariableName();           //ImportResponseWrapper name
+        String brName = ctxt.getTemporaryVariableName();            //BufferedReader name
+        String sbName = ctxt.getTemporaryVariableName();            //StringBuilder name
+        String tempStringName = ctxt.getTemporaryVariableName();
+        
+        //is absolute url
+        ctxt.generateJavaSource("boolean " + iauName + ";");
+        
+        //get the url value
+        ctxt.generateJavaSource("String " + urlName + " = ");
+        ctxt.generateAttribute("url");
+        ctxt.generateJavaSource(";");
+        
+        //validate the url
+        ctxt.generateJavaSource("if(" + urlName + " == null || " + urlName + ".equals(\"\")){");
+        ctxt.generateJavaSource("    throw new JspTagException(\"The \\\"url\\\" attribute " +
+        "illegally evaluated to \\\"null\\\" or \\\"\\\" in &lt;import&gt;\");");
+        ctxt.generateJavaSource("}");
+        
+        //initialize the is_absolute_url
+        ctxt.generateJavaSource(iauName + " = " +
+                "org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + urlName + ");");
+        
+        //validate the context
+        if(hasContext){
+            
+            ctxt.generateJavaSource("String " + contextName + " = ");
+            ctxt.generateAttribute("context");
+            ctxt.generateJavaSource(";");
+            
+            ctxt.generateJavaSource("if((!" + contextName + ".startsWith(\"/\")) " +
+                    "|| (!" + urlName + ".startsWith(\"/\"))){");
+            ctxt.generateJavaSource("    throw new JspTagException" +
+                    "(\"In URL tags, when the \\\"context\\\" attribute is specified, " +
+            "values of both \\\"context\\\" and \\\"url\\\" must start with \\\"/\\\".\");");
+            ctxt.generateJavaSource("}");
+            
+        }
+        
+        //define charset
+        ctxt.generateJavaSource("String " + charSetName + " = null;");
+        
+        //if the charEncoding attribute is specified
+        if(hasCharEncoding){
+            
+            //initialize the charEncoding
+            ctxt.generateJavaSource("String " + charEncodingName + " = ");
+            ctxt.generateAttribute("charEncoding");
+            ctxt.generateJavaSource(";");
+            
+            //assign appropriate value to the charset
+            ctxt.generateJavaSource("if(null != " + charEncodingName + " " +
+                    "&& !" + charEncodingName + ".equals(\"\")){");
+            ctxt.generateJavaSource("    " + charSetName + " = " 
+                    + charEncodingName + ";");
+            ctxt.generateJavaSource("}");
+        }
+        
+        //reshape the url string
+        ctxt.generateJavaSource("if(!"+iauName+"){");
+        ctxt.generateJavaSource("    if(!" + urlName + ".startsWith(\"/\")){");
+        ctxt.generateJavaSource("        String " + servletPathName + " = " +
+        "((HttpServletRequest)pageContext.getRequest()).getServletPath();");
+        ctxt.generateJavaSource("        " + urlName + " = " 
+                + servletPathName + ".substring(0," + servletPathName + ".lastIndexOf('/')) + '/' + " + urlName + ";");
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("}");
+        
+        //if the varReader attribute specified
+        if(hasVarReader){
+            
+            //get the String value of varReader
+            ctxt.generateJavaSource("String " + varReaderName + " = ");
+            ctxt.generateAttribute("varReader");
+            ctxt.generateJavaSource(";");
+            
+            //if the url is absolute url
+            ctxt.generateJavaSource("if(" + iauName + "){");
+            
+            //get the content of the target
+            ctxt.generateJavaSource("    java.net.URL " + urlObjName + " = new java.net.URL(" + urlName + ");");
+            ctxt.generateJavaSource("    java.net.URLConnection " + ucName + " = " 
+                    + urlObjName + ".openConnection();");
+            ctxt.generateJavaSource("    java.io.InputStream " + inputStreamName + " = " 
+                    + ucName + ".getInputStream();");
+            
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        String " + contentTypeName + " = " 
+                    + ucName + ".getContentType();");
+            ctxt.generateJavaSource("        if(null != " + contentTypeName + "){");
+            ctxt.generateJavaSource("            " + charSetName + " = " +
+                    "org.apache.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");");
+            ctxt.generateJavaSource("            if(" + charSetName + " == null) " 
+                    + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }else{");
+            ctxt.generateJavaSource("            " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            if(!hasCharEncoding){
+                ctxt.generateJavaSource("    String " + contentTypeName + " = " + ucName + ".getContentType();");
+            }
+            
+            //define the Reader
+            ctxt.generateJavaSource("    java.io.Reader " + tempReaderName + " = null;");
+            
+            //initialize the Reader object
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ", " + charSetName + ");");
+            ctxt.generateJavaSource("    }catch(Exception ex){");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ", org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response
+            ctxt.generateJavaSource("    if(" + ucName + " instanceof java.net.HttpURLConnection){");
+            ctxt.generateJavaSource("        int status = ((java.net.HttpURLConnection) " + ucName + ").getResponseCode();");
+            ctxt.generateJavaSource("        if(status < 200 || status > 299){");
+            ctxt.generateJavaSource("            throw new JspTagException(status + \" \" + " + urlName + ");");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            //set attribute in the page context scope
+            ctxt.generateJavaSource("    pageContext.setAttribute(" + varReaderName + ", " + tempReaderName + ");");
+            
+            //if the url is relative
+            ctxt.generateJavaSource("}else{");
+            
+            //if the url is relative, http request is needed
+            ctxt.generateJavaSource("    if (!(pageContext.getRequest() instanceof HttpServletRequest  " +
+            "&& pageContext.getResponse() instanceof HttpServletResponse)){");
+            ctxt.generateJavaSource("        throw new JspTagException(\"Relative &lt;import&gt; from non-HTTP request not allowed\");");
+            ctxt.generateJavaSource("    }");
+            
+            //get the servlet context of the context defined in the context attribute
+            ctxt.generateJavaSource("    ServletContext " + servletContextName + " = null;");
+            if(hasContext){
+                ctxt.generateJavaSource("    if(null != " + contextName + "){");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext().getContext(" + contextName + ");" );
+                ctxt.generateJavaSource("    }else{");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext();");
+                ctxt.generateJavaSource("    }");
+            }else{
+                ctxt.generateJavaSource("    " + servletContextName + " = pageContext.getServletContext();");
+            }
+            
+            //
+            ctxt.generateJavaSource("    if(" + servletContextName + " == null){");
+            if(hasContext){
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for Context: \\\" \"+" + contextName + "+\" \\\" and URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }else{
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for  URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }
+            ctxt.generateJavaSource("    }");
+            
+            //get the request dispatcher
+            ctxt.generateJavaSource("    RequestDispatcher " + requestDispatcherName + " = " + servletContextName + ".getRequestDispatcher(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            ctxt.generateJavaSource("    if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            
+            //initialize a ImportResponseWrapper to include the resource
+            ctxt.generateJavaSource("    org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());");
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("    }");
+            ctxt.generateJavaSource("    " + irwName + ".setCharEncoding(" + charSetName + ");");
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + requestDispatcherName + ".include(pageContext.getRequest(), " + irwName + ");");
+            ctxt.generateJavaSource("    }catch(java.io.IOException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(RuntimeException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(ServletException ex){");
+            ctxt.generateJavaSource("        Throwable rc = ex.getRootCause();");
+            ctxt.generateJavaSource("        if (rc == null)");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        else");
+            ctxt.generateJavaSource("            throw new JspException(rc);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response status
+            ctxt.generateJavaSource("    if(" + irwName + ".getStatus() < 200 || " + irwName + ".getStatus() > 299){");
+            ctxt.generateJavaSource("        throw new JspTagException(" + irwName + ".getStatus()+\" \" + org.apache.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+            ctxt.generateJavaSource("    }");
+            
+            //push in the page context
+            ctxt.generateJavaSource("    java.io.Reader " + tempReaderName + " = new java.io.StringReader(" + irwName + ".getString());");
+            ctxt.generateJavaSource("    pageContext.setAttribute(" + varReaderName + ", " + tempReaderName + ");");
+            
+            ctxt.generateJavaSource("}");
+            
+            //execute the body action
+            ctxt.generateBody();
+            
+            //close the reader
+            ctxt.generateJavaSource("java.io.Reader " + tempReaderName2 + " = (java.io.Reader)pageContext.getAttribute(" + varReaderName + ");");
+            ctxt.generateJavaSource("if(" + tempReaderName2 + " != null) " + tempReaderName2 + ".close();");
+            ctxt.generateJavaSource("pageContext.removeAttribute(" + varReaderName + ",1);");
+        }
+        
+        //if the varReader is not specified 
+        else{
+            
+            ctxt.generateJavaSource("pageContext.setAttribute(\"url_without_param\"," + urlName + ");");
+            ctxt.generateBody();
+            ctxt.generateJavaSource(urlName + " = (String)pageContext.getAttribute(\"url_without_param\");");
+            ctxt.generateJavaSource("pageContext.removeAttribute(\"url_without_param\");");
+            String strScope = "page";
+            if(hasScope){
+                strScope = ctxt.getConstantAttribute("scope");
+            }
+            int iScope = Util.getScope(strScope);
+            
+            ctxt.generateJavaSource("String " + tempStringName + " = null;");
+            
+            ctxt.generateJavaSource("if(" + iauName + "){");
+            
+            //get the content of the target
+            ctxt.generateJavaSource("    java.net.URL " + urlObjName + " = new java.net.URL(" + urlName + ");");
+            ctxt.generateJavaSource("    java.net.URLConnection " + ucName + " = " + urlObjName + ".openConnection();");
+            ctxt.generateJavaSource("    java.io.InputStream " + inputStreamName + " = " + ucName + ".getInputStream();");
+            ctxt.generateJavaSource("    java.io.Reader " + tempReaderName + " = null;");
+            
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        String " + contentTypeName + " = " 
+                    + ucName + ".getContentType();");
+            ctxt.generateJavaSource("        if(null != " + contentTypeName + "){");
+            ctxt.generateJavaSource("            " + charSetName + " = " +
+                    "org.apache.jasper.tagplugins.jstl.Util.getContentTypeAttribute(" + contentTypeName + ", \"charset\");");
+            ctxt.generateJavaSource("            if(" + charSetName + " == null) " 
+                    + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }else{");
+            ctxt.generateJavaSource("            " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + "," + charSetName + ");");
+            ctxt.generateJavaSource("    }catch(Exception ex){");
+            //ctxt.generateJavaSource("        throw new JspTagException(ex.toString());");
+            ctxt.generateJavaSource("        " + tempReaderName + " = new java.io.InputStreamReader(" + inputStreamName + ",org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response
+            ctxt.generateJavaSource("    if(" + ucName + " instanceof java.net.HttpURLConnection){");
+            ctxt.generateJavaSource("        int status = ((java.net.HttpURLConnection) " + ucName + ").getResponseCode();");
+            ctxt.generateJavaSource("        if(status < 200 || status > 299){");
+            ctxt.generateJavaSource("            throw new JspTagException(status + \" \" + " + urlName + ");");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            
+            ctxt.generateJavaSource("    java.io.BufferedReader " + brName + " =  new java.io.BufferedReader(" + tempReaderName + ");");
+            ctxt.generateJavaSource("    StringBuilder " + sbName + " = new StringBuilder();");
+            String index = ctxt.getTemporaryVariableName();
+            ctxt.generateJavaSource("    int " + index + ";");
+            ctxt.generateJavaSource("    while(("+index+" = "+brName+".read()) != -1) "+sbName+".append((char)"+index+");");
+            ctxt.generateJavaSource("    " + tempStringName + " = " +sbName + ".toString();");
+            
+            ctxt.generateJavaSource("}else{");
+            
+            //if the url is relative, http request is needed.
+            ctxt.generateJavaSource("    if (!(pageContext.getRequest() instanceof HttpServletRequest  " +
+            "&& pageContext.getResponse() instanceof HttpServletResponse)){");
+            ctxt.generateJavaSource("        throw new JspTagException(\"Relative &lt;import&gt; from non-HTTP request not allowed\");");
+            ctxt.generateJavaSource("    }");
+            
+            //get the servlet context of the context defined in the context attribute
+            ctxt.generateJavaSource("    ServletContext " + servletContextName + " = null;");
+            if(hasContext){
+                ctxt.generateJavaSource("    if(null != " + contextName + "){");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext().getContext(" + contextName + ");" );
+                ctxt.generateJavaSource("    }else{");
+                ctxt.generateJavaSource("        " + servletContextName + " = pageContext.getServletContext();");
+                ctxt.generateJavaSource("    }");
+            }else{
+                ctxt.generateJavaSource("    " + servletContextName + " = pageContext.getServletContext();");
+            }
+            
+            //
+            ctxt.generateJavaSource("    if(" + servletContextName + " == null){");
+            if(hasContext){
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for Context: \\\" \" +" + contextName + "+ \" \\\" and URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }else{
+                ctxt.generateJavaSource("        throw new JspTagException(\"Unable to get RequestDispatcher for URL: \\\" \" +" + urlName + "+ \" \\\". Verify values and/or enable cross context access.\");");
+            }
+            ctxt.generateJavaSource("    }");
+            
+            //get the request dispatcher
+            ctxt.generateJavaSource("    RequestDispatcher " + requestDispatcherName + " = " + servletContextName + ".getRequestDispatcher(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            ctxt.generateJavaSource("    if(" + requestDispatcherName + " == null) throw new JspTagException(org.apache.jasper.tagplugins.jstl.Util.stripSession("+urlName+"));");
+            
+            //initialize a ImportResponseWrapper to include the resource
+            ctxt.generateJavaSource("    org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper " + irwName + " = new org.apache.jasper.tagplugins.jstl.Util.ImportResponseWrapper((HttpServletResponse) pageContext.getResponse());");
+            ctxt.generateJavaSource("    if(" + charSetName + " == null){");
+            ctxt.generateJavaSource("        " + charSetName + " = org.apache.jasper.tagplugins.jstl.Util.DEFAULT_ENCODING;");
+            ctxt.generateJavaSource("    }");
+            ctxt.generateJavaSource("    " + irwName + ".setCharEncoding(" + charSetName + ");");
+            ctxt.generateJavaSource("    try{");
+            ctxt.generateJavaSource("        " + requestDispatcherName + ".include(pageContext.getRequest(), " + irwName + ");");
+            ctxt.generateJavaSource("    }catch(java.io.IOException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(RuntimeException ex){");
+            ctxt.generateJavaSource("        throw new JspException(ex);");
+            ctxt.generateJavaSource("    }catch(ServletException ex){");
+            ctxt.generateJavaSource("        Throwable rc = ex.getRootCause();");
+            ctxt.generateJavaSource("        if (rc == null)");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        else");
+            ctxt.generateJavaSource("            throw new JspException(rc);");
+            ctxt.generateJavaSource("    }");
+            
+            //validate the response status
+            ctxt.generateJavaSource("    if(" + irwName + ".getStatus() < 200 || " + irwName + ".getStatus() > 299){");
+            ctxt.generateJavaSource("        throw new JspTagException(" + irwName + ".getStatus()+\" \" + org.apache.jasper.tagplugins.jstl.Util.stripSession(" + urlName + "));");
+            ctxt.generateJavaSource("    }");
+            
+            ctxt.generateJavaSource("    " + tempStringName + " = " + irwName + ".getString();");
+            
+            ctxt.generateJavaSource("}");
+            
+            if(hasVar){
+                String strVar = ctxt.getConstantAttribute("var");
+                ctxt.generateJavaSource("pageContext.setAttribute(\""+strVar+"\"," + tempStringName + "," + iScope + ");");
+            }else{
+                ctxt.generateJavaSource("pageContext.getOut().print(" + tempStringName + ");");
+            }
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Otherwise.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Otherwise.java
new file mode 100644
index 0000000..11afe2c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Otherwise.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public final class Otherwise implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        // See When.java for the reason whey "}" is need at the beginng and
+        // not at the end.
+        ctxt.generateJavaSource("} else {");
+        ctxt.generateBody();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Out.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Out.java
new file mode 100644
index 0000000..58d3a9e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Out.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+
+public final class Out implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        //these two data member are to indicate 
+        //whether the corresponding attribute is specified
+        boolean hasDefault=false, hasEscapeXml=false;
+        hasDefault = ctxt.isAttributeSpecified("default");
+        hasEscapeXml = ctxt.isAttributeSpecified("escapeXml");
+        
+        //strValName, strEscapeXmlName & strDefName are two variables' name 
+        //standing for value, escapeXml and default attribute
+        String strValName = ctxt.getTemporaryVariableName();
+        String strDefName = ctxt.getTemporaryVariableName();
+        String strEscapeXmlName = ctxt.getTemporaryVariableName();
+        
+        //according to the tag file, the value attribute is mandatory.
+        ctxt.generateJavaSource("String " + strValName + " = null;");
+        ctxt.generateJavaSource("if(");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource("!=null){");
+        ctxt.generateJavaSource("    " + strValName + " = (");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource(").toString();");
+        ctxt.generateJavaSource("}");
+        
+        //initiate the strDefName with null.
+        //if the default has been specified, then assign the value to it;
+        ctxt.generateJavaSource("String " + strDefName + " = null;\n");
+        if(hasDefault){
+            ctxt.generateJavaSource("if(");
+            ctxt.generateAttribute("default");
+            ctxt.generateJavaSource(" != null){");
+            ctxt.generateJavaSource(strDefName + " = (");
+            ctxt.generateAttribute("default");
+            ctxt.generateJavaSource(").toString();");
+            ctxt.generateJavaSource("}");
+        }
+        
+        //initiate the strEscapeXmlName with true;
+        //if the escapeXml is specified, assign the value to it;
+        ctxt.generateJavaSource("boolean " + strEscapeXmlName + " = true;");
+        if(hasEscapeXml){
+            ctxt.generateJavaSource(strEscapeXmlName + " = Boolean.parseBoolean((");
+            ctxt.generateAttribute("default");
+            ctxt.generateJavaSource(").toString());");
+        }
+        
+        //main part. 
+        ctxt.generateJavaSource("if(null != " + strValName +"){");
+        ctxt.generateJavaSource("    if(" + strEscapeXmlName + "){");
+        ctxt.generateJavaSource("        " + strValName + " = org.apache.jasper.tagplugins.jstl.Util.escapeXml(" + strValName + ");");
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("    out.write(" + strValName + ");");
+        ctxt.generateJavaSource("}else{");
+        ctxt.generateJavaSource("    if(null != " + strDefName + "){");
+        ctxt.generateJavaSource("        if(" + strEscapeXmlName + "){");
+        ctxt.generateJavaSource("            " + strDefName + " = org.apache.jasper.tagplugins.jstl.Util.escapeXml(" + strDefName + ");");
+        ctxt.generateJavaSource("        }");
+        ctxt.generateJavaSource("        out.write(" + strDefName + ");");
+        ctxt.generateJavaSource("    }else{");
+        ctxt.generateBody();
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("}");   
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Param.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Param.java
new file mode 100644
index 0000000..43a7e12
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Param.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class Param implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        //don't support the body content
+        
+        //define names of all the temp variables
+        String nameName = ctxt.getTemporaryVariableName();
+        String valueName = ctxt.getTemporaryVariableName();
+        String urlName = ctxt.getTemporaryVariableName();
+        String encName = ctxt.getTemporaryVariableName();
+        String index = ctxt.getTemporaryVariableName();
+        
+        //if the param tag has no parents, throw a exception
+        TagPluginContext parent = ctxt.getParentContext();
+        if(parent == null){
+            ctxt.generateJavaSource(" throw new JspTagExcption" +
+            "(\"&lt;param&gt; outside &lt;import&gt; or &lt;urlEncode&gt;\");");
+            return;
+        }
+        
+        //get the url string before adding this param
+        ctxt.generateJavaSource("String " + urlName + " = " +
+        "(String)pageContext.getAttribute(\"url_without_param\");");
+        
+        //get the value of "name"
+        ctxt.generateJavaSource("String " + nameName + " = ");
+        ctxt.generateAttribute("name");
+        ctxt.generateJavaSource(";");
+        
+        //if the "name" is null then do nothing.
+        //else add such string "name=value" to the url.
+        //and the url should be encoded
+        ctxt.generateJavaSource("if(" + nameName + " != null && !" + nameName + ".equals(\"\")){");
+        ctxt.generateJavaSource("    String " + valueName + " = ");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource(";");
+        ctxt.generateJavaSource("    if(" + valueName + " == null) " + valueName + " = \"\";");
+        ctxt.generateJavaSource("    String " + encName + " = pageContext.getResponse().getCharacterEncoding();");
+        ctxt.generateJavaSource("    " + nameName + " = java.net.URLEncoder.encode(" + nameName + ", " + encName + ");");
+        ctxt.generateJavaSource("    " + valueName + " = java.net.URLEncoder.encode(" + valueName + ", " + encName + ");");
+        ctxt.generateJavaSource("    int " + index + ";");
+        ctxt.generateJavaSource("    " + index + " = " + urlName + ".indexOf(\'?\');");
+        //if the current param is the first one, add a "?" ahead of it
+        //else add a "&" ahead of it
+        ctxt.generateJavaSource("    if(" + index + " == -1){");
+        ctxt.generateJavaSource("        " + urlName + " = " + urlName + " + \"?\" + " + nameName + " + \"=\" + " + valueName + ";");
+        ctxt.generateJavaSource("    }else{");
+        ctxt.generateJavaSource("        " + urlName + " = " + urlName + " + \"&\" + " + nameName + " + \"=\" + " + valueName + ";");
+        ctxt.generateJavaSource("    }");
+        ctxt.generateJavaSource("    pageContext.setAttribute(\"url_without_param\"," + urlName + ");");
+        ctxt.generateJavaSource("}");
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Redirect.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Redirect.java
new file mode 100644
index 0000000..0390dc1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Redirect.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public class Redirect implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        //flag for the existence of the "context"
+        boolean hasContext = ctxt.isAttributeSpecified("context");
+        
+        //names of the temp variables
+        String urlName = ctxt.getTemporaryVariableName();
+        String contextName = ctxt.getTemporaryVariableName();
+        String baseUrlName = ctxt.getTemporaryVariableName();
+        String resultName = ctxt.getTemporaryVariableName();
+        String responseName = ctxt.getTemporaryVariableName();
+        
+        //get context
+        ctxt.generateJavaSource("String " + contextName + " = null;");
+        if(hasContext){
+            ctxt.generateJavaSource(contextName + " = ");
+            ctxt.generateAttribute("context");
+            ctxt.generateJavaSource(";");
+        }
+        
+        //get the url
+        ctxt.generateJavaSource("String " + urlName + " = ");
+        ctxt.generateAttribute("url");
+        ctxt.generateJavaSource(";");
+        
+        //get the raw url according to "url" and "context"
+        ctxt.generateJavaSource("String " + baseUrlName + " = " +
+                "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + urlName + ", " + contextName + ", pageContext);");
+        ctxt.generateJavaSource("pageContext.setAttribute" +
+                "(\"url_without_param\", " + baseUrlName + ");");
+        
+        //add params
+        ctxt.generateBody();
+        
+        ctxt.generateJavaSource("String " + resultName + " = " +
+        "(String)pageContext.getAttribute(\"url_without_param\");");
+        ctxt.generateJavaSource("pageContext.removeAttribute" +
+        "(\"url_without_param\");");
+        
+        //get the response object
+        ctxt.generateJavaSource("HttpServletResponse " + responseName + " = " +
+        "((HttpServletResponse) pageContext.getResponse());");
+        
+        //if the url is relative, encode it
+        ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){");
+        ctxt.generateJavaSource("    " + resultName + " = "
+                + responseName + ".encodeRedirectURL(" + resultName + ");");
+        ctxt.generateJavaSource("}");
+        
+        //do redirect
+        ctxt.generateJavaSource("try{");
+        ctxt.generateJavaSource("    " + responseName + ".sendRedirect(" + resultName + ");");
+        ctxt.generateJavaSource("}catch(java.io.IOException ex){");
+        ctxt.generateJavaSource("    throw new JspTagException(ex.toString(), ex);");
+        ctxt.generateJavaSource("}");
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Remove.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Remove.java
new file mode 100644
index 0000000..f7e8404
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Remove.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Remove implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        //scope flag
+        boolean hasScope = ctxt.isAttributeSpecified("scope");
+        
+        //the value of the "var"
+        String strVar = ctxt.getConstantAttribute("var");
+        
+        //remove attribute from certain scope.
+        //default scope is "page".
+        if(hasScope){
+            int iScope = Util.getScope(ctxt.getConstantAttribute("scope"));
+            ctxt.generateJavaSource("pageContext.removeAttribute(\"" + strVar + "\"," + iScope + ");");
+        }else{
+            ctxt.generateJavaSource("pageContext.removeAttribute(\"" + strVar + "\");");
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Set.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Set.java
new file mode 100644
index 0000000..04341d4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Set.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Set implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        //the flags to indicate whether the attributes have been specified
+        boolean hasValue = false, hasVar = false, hasScope = false, 
+        hasTarget = false;
+        
+        //the scope name
+        String strScope;
+        //the id of the scope
+        int iScope;
+        
+        //initialize the flags
+        hasValue = ctxt.isAttributeSpecified("value");
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasScope = ctxt.isAttributeSpecified("scope");
+        hasTarget = ctxt.isAttributeSpecified("target");
+        
+        //the temp variables name
+        String resultName = ctxt.getTemporaryVariableName();
+        String targetName = ctxt.getTemporaryVariableName();
+        String propertyName = ctxt.getTemporaryVariableName();
+        
+        //initialize the "result" which will be assigned to the var or target.property
+        ctxt.generateJavaSource("Object " + resultName + " = null;");
+        if(hasValue){
+            ctxt.generateJavaSource(resultName + " = ");
+            ctxt.generateAttribute("value");
+            ctxt.generateJavaSource(";");
+        }else{
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        //initialize the strScope
+        if(hasScope){
+            strScope = ctxt.getConstantAttribute("scope");
+        }else{
+            strScope = "page";
+        }
+        
+        //get the iScope according to the strScope
+        iScope = Util.getScope(strScope);
+        
+        //if the attribute var has been specified then assign the result to the var;
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("if(null != " + resultName + "){");
+            ctxt.generateJavaSource("    pageContext.setAttribute(\"" + strVar + "\"," + resultName + "," + iScope + ");");
+            ctxt.generateJavaSource("} else {");
+            if(hasScope){
+                ctxt.generateJavaSource("    pageContext.removeAttribute(\"" + strVar + "\"," + iScope + ");");
+            }else{
+                ctxt.generateJavaSource("    pageContext.removeAttribute(\"" + strVar + "\");");
+            }
+            ctxt.generateJavaSource("}");
+            
+            //else assign the result to the target.property
+        }else if(hasTarget){
+            
+            //generate the temp variable name
+            String pdName = ctxt.getTemporaryVariableName();
+            String successFlagName = ctxt.getTemporaryVariableName();
+            String index = ctxt.getTemporaryVariableName();
+            String methodName = ctxt.getTemporaryVariableName();
+            
+            //initialize the property
+            ctxt.generateJavaSource("String " + propertyName + " = null;");
+            ctxt.generateJavaSource("if(");
+            ctxt.generateAttribute("property");
+            ctxt.generateJavaSource(" != null){");
+            ctxt.generateJavaSource("    " + propertyName + " = (");
+            ctxt.generateAttribute("property");
+            ctxt.generateJavaSource(").toString();");
+            ctxt.generateJavaSource("}");
+            
+            //initialize the target
+            ctxt.generateJavaSource("Object " + targetName + " = ");
+            ctxt.generateAttribute("target");
+            ctxt.generateJavaSource(";");
+            
+            //the target is ok
+            ctxt.generateJavaSource("if(" + targetName + " != null){");
+            
+            //if the target is a map, then put the result into the map with the key property
+            ctxt.generateJavaSource("    if(" + targetName + " instanceof java.util.Map){");
+            ctxt.generateJavaSource("        if(null != " + resultName + "){");
+            ctxt.generateJavaSource("            ((java.util.Map) " + targetName + ").put(" + propertyName + "," + resultName + ");");
+            ctxt.generateJavaSource("        }else{");
+            ctxt.generateJavaSource("            ((java.util.Map) " + targetName + ").remove(" + propertyName + ");");
+            ctxt.generateJavaSource("        }");
+            
+            //else assign the result to the target.property
+            ctxt.generateJavaSource("    }else{");
+            ctxt.generateJavaSource("        try{");
+            
+            //get all the property of the target
+            ctxt.generateJavaSource("            java.beans.PropertyDescriptor " + pdName + "[] = java.beans.Introspector.getBeanInfo(" + targetName + ".getClass()).getPropertyDescriptors();");
+            
+            //the success flag is to imply whether the assign is successful
+            ctxt.generateJavaSource("            boolean " + successFlagName + " = false;");
+            
+            //find the right property
+            ctxt.generateJavaSource("            for(int " + index + "=0;" + index + "<" + pdName + ".length;" + index + "++){");
+            ctxt.generateJavaSource("                if(" + pdName + "[" + index + "].getName().equals(" + propertyName + ")){");
+            
+            //get the "set" method;
+            ctxt.generateJavaSource("                    java.lang.reflect.Method " + methodName + " = " + pdName + "[" + index + "].getWriteMethod();");
+            ctxt.generateJavaSource("                    if(null == " + methodName + "){");
+            ctxt.generateJavaSource("                        throw new JspException(\"No setter method in &lt;set&gt; for property \"+" + propertyName + ");");
+            ctxt.generateJavaSource("                    }");
+            
+            //invoke the method through the reflection
+            ctxt.generateJavaSource("                    if(" + resultName + " != null){");
+            ctxt.generateJavaSource("                        " + methodName + ".invoke(" + targetName + ", new Object[]{(" + methodName + ".getParameterTypes()[0]).cast(" + resultName + ")});");
+            ctxt.generateJavaSource("                    }else{");
+            ctxt.generateJavaSource("                        " + methodName + ".invoke(" + targetName + ", new Object[]{null});");
+            ctxt.generateJavaSource("                    }");
+            ctxt.generateJavaSource("                    " + successFlagName + " = true;");
+            ctxt.generateJavaSource("                }");
+            ctxt.generateJavaSource("            }");
+            ctxt.generateJavaSource("            if(!" + successFlagName + "){");
+            ctxt.generateJavaSource("                throw new JspException(\"Invalid property in &lt;set&gt;:\"+" + propertyName + ");");
+            ctxt.generateJavaSource("            }");
+            ctxt.generateJavaSource("        }");
+            
+            //catch the el exception and throw it as a JspException
+            ctxt.generateJavaSource("        catch (IllegalAccessException ex) {");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        } catch (java.beans.IntrospectionException ex) {");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        } catch (java.lang.reflect.InvocationTargetException ex) {");
+            ctxt.generateJavaSource("            throw new JspException(ex);");
+            ctxt.generateJavaSource("        }");
+            ctxt.generateJavaSource("    }");
+            ctxt.generateJavaSource("}else{");
+            ctxt.generateJavaSource("    throw new JspException();");
+            ctxt.generateJavaSource("}");
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Url.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Url.java
new file mode 100644
index 0000000..f7b79fa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/Url.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.tagplugins.jstl.Util;
+
+public class Url implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        
+        //flags
+        boolean hasVar, hasContext, hasScope;
+        
+        //init flags
+        hasVar = ctxt.isAttributeSpecified("var");
+        hasContext = ctxt.isAttributeSpecified("context");
+        hasScope = ctxt.isAttributeSpecified("scope");
+        
+        //define name of the temp variables
+        String valueName = ctxt.getTemporaryVariableName();
+        String contextName = ctxt.getTemporaryVariableName();
+        String baseUrlName = ctxt.getTemporaryVariableName();
+        String resultName = ctxt.getTemporaryVariableName();
+        String responseName = ctxt.getTemporaryVariableName();
+        
+        //get the scope
+        String strScope = "page";
+        if(hasScope){
+            strScope = ctxt.getConstantAttribute("scope");
+        }
+        int iScope = Util.getScope(strScope);
+        
+        //get the value
+        ctxt.generateJavaSource("String " + valueName + " = ");
+        ctxt.generateAttribute("value");
+        ctxt.generateJavaSource(";");
+        
+        //get the context
+        ctxt.generateJavaSource("String " + contextName + " = null;");
+        if(hasContext){
+            ctxt.generateJavaSource(contextName + " = ");
+            ctxt.generateAttribute("context");
+            ctxt.generateJavaSource(";");
+        }
+        
+        //get the raw url
+        ctxt.generateJavaSource("String " + baseUrlName + " = " +
+                "org.apache.jasper.tagplugins.jstl.Util.resolveUrl(" + valueName + ", " + contextName + ", pageContext);");
+        ctxt.generateJavaSource("pageContext.setAttribute" +
+                "(\"url_without_param\", " + baseUrlName + ");");
+        
+        //add params
+        ctxt.generateBody();
+        
+        ctxt.generateJavaSource("String " + resultName + " = " +
+        "(String)pageContext.getAttribute(\"url_without_param\");");
+        ctxt.generateJavaSource("pageContext.removeAttribute(\"url_without_param\");");
+        
+        //if the url is relative, encode it
+        ctxt.generateJavaSource("if(!org.apache.jasper.tagplugins.jstl.Util.isAbsoluteUrl(" + resultName + ")){");
+        ctxt.generateJavaSource("    HttpServletResponse " + responseName + " = " +
+        "((HttpServletResponse) pageContext.getResponse());");
+        ctxt.generateJavaSource("    " + resultName + " = "
+                + responseName + ".encodeURL(" + resultName + ");");
+        ctxt.generateJavaSource("}");
+        
+        //if "var" is specified, the url string store in the attribute var defines
+        if(hasVar){
+            String strVar = ctxt.getConstantAttribute("var");
+            ctxt.generateJavaSource("pageContext.setAttribute" +
+                    "(\"" + strVar + "\", " + resultName + ", " + iScope + ");");
+            
+            //if var is not specified, just print out the url string
+        }else{
+            ctxt.generateJavaSource("try{");
+            ctxt.generateJavaSource("    pageContext.getOut().print(" + resultName + ");");
+            ctxt.generateJavaSource("}catch(java.io.IOException ex){");
+            ctxt.generateJavaSource("    throw new JspTagException(ex.toString(), ex);");
+            ctxt.generateJavaSource("}");
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/When.java b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/When.java
new file mode 100644
index 0000000..e44e1ae
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/core/When.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+public final class When implements TagPlugin {
+    
+    @Override
+    public void doTag(TagPluginContext ctxt) {
+        // Get the parent context to determine if this is the first <c:when>
+        TagPluginContext parentContext = ctxt.getParentContext();
+        if (parentContext == null) {
+            ctxt.dontUseTagPlugin();
+            return;
+        }
+        
+        if ("true".equals(parentContext.getPluginAttribute("hasBeenHere"))) {
+            ctxt.generateJavaSource("} else if(");
+            // See comment below for the reason we generate the extra "}" here.
+        }
+        else {
+            ctxt.generateJavaSource("if(");
+            parentContext.setPluginAttribute("hasBeenHere", "true");
+        }
+        ctxt.generateAttribute("test");
+        ctxt.generateJavaSource("){");
+        ctxt.generateBody();
+        
+        // We don't generate the closing "}" for the "if" here because there
+        // may be whitespaces in between <c:when>'s.  Instead we delay
+        // generating it until the next <c:when> or <c:otherwise> or
+        // <c:choose>
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/tagPlugins.xml b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/tagPlugins.xml
new file mode 100644
index 0000000..f503c8b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/tagplugins/jstl/tagPlugins.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<tag-plugins>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.IfTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.If</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.ChooseTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Choose</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.WhenTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.When</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.OtherwiseTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Otherwise</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForEachTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.ForEach</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Out</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.SetTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Set</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.RemoveTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Remove</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Catch</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForTokensTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.ForTokens</plugin-class>
+  </tag-plugin>
+  <tag-plugin>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ImportTag</tag-class>
+    <plugin-class>org.apache.jasper.tagplugins.jstl.core.Import</plugin-class>
+  </tag-plugin>
+</tag-plugins>
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/util/Enumerator.java b/bundles/org.apache.tomcat/src/org/apache/jasper/util/Enumerator.java
new file mode 100644
index 0000000..537c336
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/util/Enumerator.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.jasper.util;
+
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+
+/**
+ * Adapter class that wraps an <code>Enumeration</code> around a Java2
+ * collection classes object <code>Iterator</code> so that existing APIs
+ * returning Enumerations can easily run on top of the new collections.
+ * Constructors are provided to easily create such wrappers.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Enumerator.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ */
+
+public final class Enumerator<T> implements Enumeration<T> {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     */
+    public Enumerator(Iterator<T> iterator) {
+
+        super();
+        this.iterator = iterator;
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values returned by the
+     * specified Iterator.
+     *
+     * @param iterator Iterator to be wrapped
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Iterator<T> iterator, boolean clone) {
+
+        super();
+        if (!clone) {
+            this.iterator = iterator;
+        } else {
+            List<T> list = new ArrayList<T>();
+            while (iterator.hasNext()) {
+                list.add(iterator.next());
+            }
+            this.iterator = list.iterator();   
+        }
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The <code>Iterator</code> over which the <code>Enumeration</code>
+     * represented by this class actually operates.
+     */
+    private Iterator<T> iterator = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Tests if this enumeration contains more elements.
+     *
+     * @return <code>true</code> if and only if this enumeration object
+     *  contains at least one more element to provide, <code>false</code>
+     *  otherwise
+     */
+    @Override
+    public boolean hasMoreElements() {
+
+        return (iterator.hasNext());
+
+    }
+
+
+    /**
+     * Returns the next element of this enumeration if this enumeration
+     * has at least one more element to provide.
+     *
+     * @return the next element of this enumeration
+     *
+     * @exception NoSuchElementException if no more elements exist
+     */
+    @Override
+    public T nextElement() throws NoSuchElementException {
+
+        return (iterator.next());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/util/ExceptionUtils.java b/bundles/org.apache.tomcat/src/org/apache/jasper/util/ExceptionUtils.java
new file mode 100644
index 0000000..be5eeb8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/util/ExceptionUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.util;
+
+/**
+ * Utilities for handling Throwables and Exceptions.
+ */
+public class ExceptionUtils {
+    
+    /**
+     * Checks whether the supplied Throwable is one that needs to be 
+     * rethrown and swallows all others.
+     * @param t the Throwable to check
+     */
+    public static void handleThrowable(Throwable t) {
+        if (t instanceof ThreadDeath) {
+            throw (ThreadDeath) t;
+        }
+        if (t instanceof VirtualMachineError) {
+            throw (VirtualMachineError) t;
+        }
+        // All other instances of Throwable will be silently swallowed
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/util/FastRemovalDequeue.java b/bundles/org.apache.tomcat/src/org/apache/jasper/util/FastRemovalDequeue.java
new file mode 100644
index 0000000..74a65fd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/util/FastRemovalDequeue.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.util;
+
+/**
+ * 
+ * The FastRemovalDequeue is a Dequeue that supports constant time removal of
+ * entries. This is achieved by using a doubly linked list and wrapping any object
+ * added to the collection with an Entry type, that is returned to the consumer.
+ * When removing an object from the list, the consumer provides this Entry object.
+ *
+ * The Entry type is nearly opaque to the consumer of the queue. The only public
+ * member is the getter for any object displaced when adding a new object to the
+ * queue. This can be used to destroy that object.
+ *
+ * The Entry object contains the links pointing to the neighbours in the doubly
+ * linked list, so that removal of an Entry does not need to search for it but
+ * instead can be done in constant time.
+ *
+ * The implementation is fully thread-safe.
+ *
+ * Invalidation of Entry objects during removal from the list is done
+ * by setting their "valid" field to false. All public methods which take Entry
+ * objects as arguments are NOP if the entry is no longer valid.
+ *
+ * A typical use of the FastRemovalDequeue is a list of entries in sorted order,
+ * where the sort position of an object will only switch to first or last.
+ *
+ * Whenever the sort position needs to change, the consumer can remove the object
+ * and reinsert it in front or at the end in constant time.
+ * So keeping the list sorted is very cheap.
+ *
+ */
+public class FastRemovalDequeue<T> {
+
+    /** Maximum size of the queue */
+    private final int maxSize;
+    /** First element of the queue. */
+    private Entry first;
+    /** Last element of the queue. */
+    private Entry last;
+    /** Size of the queue */
+    private int size;
+
+    /** Initialize empty queue. */
+    public FastRemovalDequeue(int maxSize) {
+        if (maxSize <=1 ) {
+            maxSize = 2;
+        }
+        this.maxSize = maxSize;
+        first = null;
+        last = null;
+        size = 0;
+    }
+
+    /**
+     * Retrieve the size of the list.
+     * This method also needs to be externaly synchronized to
+     * ensure correct publication of changes.
+     * 
+     * @return the size of the list.
+     * */
+    public synchronized int getSize() {
+        return size;
+    }
+
+    /**
+     * Adds an object to the start of the list and returns the entry created for
+     * said object. The entry can later be reused for moving the entry.
+     * 
+     * @param object the object to prepend to the start of the list.
+     * @return an entry for use when the object should be moved.
+     * */
+    public synchronized Entry push(final T object) {
+        Entry entry = new Entry(object);
+        if (size >= maxSize) {
+            entry.setReplaced(pop());
+        }
+        if (first == null) {
+            first = last = entry;
+        } else {
+            first.setPrevious(entry);
+            entry.setNext(first);
+            first = entry;
+        }
+        size++;
+
+        return entry;
+    }
+
+    /**
+     * Adds an object to the end of the list and returns the entry created for
+     * said object. The entry can later be reused for moving the entry.
+     * 
+     * @param object the object to append to the end of the list.
+     * @return an entry for use when the object should be moved.
+     * */
+    public synchronized Entry unpop(final T object) {
+        Entry entry = new Entry(object);
+        if (size >= maxSize) {
+            entry.setReplaced(unpush());
+        }
+        if (first == null) {
+            first = last = entry;
+        } else {
+            last.setNext(entry);
+            entry.setPrevious(last);
+            last = entry;
+        }
+        size++;
+
+        return entry;
+    }
+
+    /**
+     * Removes the first element of the list and returns its content.
+     * 
+     * @return the content of the first element of the list.
+     **/
+    public synchronized T unpush() {
+        T content = null;
+        if (first != null) {
+            Entry element = first;
+            first = first.getNext();
+            content = element.getContent();
+            if (first != null) {
+                first.setPrevious(null);
+            }
+            size--;
+            element.setValid(false);
+        }
+        return content;
+    }
+
+    /**
+     * Removes the last element of the list and returns its content.
+     * 
+     * @return the content of the last element of the list.
+     **/
+    public synchronized T pop() {
+        T content = null;
+        if (last != null) {
+            Entry element = last;
+            last = last.getPrevious();
+            content = element.getContent();
+            if (last != null) {
+                last.setNext(null);
+            }
+            size--;
+            element.setValid(false);
+        }
+        return content;
+    }
+
+    /**
+     * Removes any element of the list and returns its content.
+     **/
+    public synchronized void remove(final Entry element) {
+        if (!element.getValid()) {
+            return;
+        }
+        Entry next = element.getNext();
+        Entry prev = element.getPrevious();
+        if (next != null) {
+            next.setPrevious(prev);
+        } else {
+            last = prev;
+        }
+        if (prev != null) {
+            prev.setNext(next);
+        } else {
+            first = next;
+        }
+        size--;
+        element.setValid(false);
+    }
+
+    /**
+     * Moves the element in front.
+     *
+     * Could also be implemented as remove() and
+     * push(), but explicitely coding might be a bit faster.
+     * 
+     * @param element the entry to move in front.
+     * */
+    public synchronized void moveFirst(final Entry element) {
+        if (element.getValid() &&
+            element.getPrevious() != null) {
+            Entry prev = element.getPrevious();
+            Entry next = element.getNext();
+            prev.setNext(next);
+            if (next != null) {
+                next.setPrevious(prev);
+            } else {
+                last = prev;
+            }
+            first.setPrevious(element);
+            element.setNext(first);
+            element.setPrevious(null);
+            first = element;
+        }
+    }
+
+    /**
+     * Moves the element to the back.
+     *
+     * Could also be implemented as remove() and
+     * unpop(), but explicitely coding might be a bit faster.
+     * 
+     * @param element the entry to move to the back.
+     * */
+    public synchronized void moveLast(final Entry element) {
+        if (element.getValid() &&
+            element.getNext() != null) {
+            Entry next = element.getNext();
+            Entry prev = element.getPrevious();
+            next.setPrevious(prev);
+            if (prev != null) {
+                prev.setNext(next);
+            } else {
+                first = next;
+            }
+            last.setNext(element);
+            element.setPrevious(last);
+            element.setNext(null);
+            last = element;
+        }
+    }
+
+    /**
+     * Implementation of a doubly linked list entry.
+     * All implementation details are private.
+     * For the consumer of the above collection, this
+     * is simply garbage in, garbage out.
+     */
+    public class Entry {
+
+        /** Is this entry still valid? */
+        private boolean valid = true;
+        /** The content this entry is valid for. */
+        private final T content;
+        /** Optional content that was displaced by this entry */
+        private T replaced = null;
+        /** Pointer to next element in queue. */
+        private Entry next = null;
+        /** Pointer to previous element in queue. */
+        private Entry previous = null;
+
+        private Entry(T object) {
+            content = object;
+        }
+
+        private final boolean getValid() {
+            return valid;
+        }
+
+        private final void setValid(final boolean valid) {
+            this.valid = valid;
+        }
+
+        public final T getContent() {
+            return content;
+        }
+
+        public final T getReplaced() {
+            return replaced;
+        }
+
+        private final void setReplaced(final T replaced) {
+            this.replaced = replaced;
+        }
+
+        private final Entry getNext() {
+            return next;
+        }
+
+        private final void setNext(final Entry next) {
+            this.next = next;
+        }
+
+        private final Entry getPrevious() {
+            return previous;
+        }
+
+        private final void setPrevious(final Entry previous) {
+            this.previous = previous;
+        }
+
+        @Override
+        public String toString() {
+            return "Entry-" + content.toString();
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/util/UniqueAttributesImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/util/UniqueAttributesImpl.java
new file mode 100644
index 0000000..67da6d1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/util/UniqueAttributesImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.util;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.jasper.compiler.Localizer;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Wraps the default attributes implementation and ensures that each attribute
+ * has a unique qname as required by the JSP specification.
+ */
+public class UniqueAttributesImpl extends AttributesImpl {
+
+    private static final String IMPORT = "import";
+    private static final String PAGE_ENCODING = "pageEncoding";
+    
+    private final boolean pageDirective;
+    private final Set<String> qNames = new HashSet<String>();
+
+    public UniqueAttributesImpl() {
+        this.pageDirective = false;
+    }
+
+    public UniqueAttributesImpl(boolean pageDirective) {
+        this.pageDirective = pageDirective;
+    }
+
+    @Override
+    public void clear() {
+        qNames.clear();
+        super.clear();
+    }
+
+    @Override
+    public void setAttributes(Attributes atts) {
+        for (int i = 0; i < atts.getLength(); i++) {
+            if (!qNames.add(atts.getQName(i))) {
+                handleDuplicate(atts.getQName(i), atts.getValue(i));
+            }
+        }
+        super.setAttributes(atts);
+    }
+
+    @Override
+    public void addAttribute(String uri, String localName, String qName,
+            String type, String value) {
+        if (qNames.add(qName)) {
+            super.addAttribute(uri, localName, qName, type, value);
+        } else {
+            handleDuplicate(qName, value);
+        }
+    }
+
+    @Override
+    public void setAttribute(int index, String uri, String localName,
+            String qName, String type, String value) {
+        qNames.remove(super.getQName(index));
+        if (qNames.add(qName)) {
+            super.setAttribute(index, uri, localName, qName, type, value);
+        } else {
+            handleDuplicate(qName, value);
+        }
+    }
+
+    @Override
+    public void removeAttribute(int index) {
+        qNames.remove(super.getQName(index));
+        super.removeAttribute(index);
+    }
+
+    @Override
+    public void setQName(int index, String qName) {
+        qNames.remove(super.getQName(index));
+        super.setQName(index, qName);
+    }
+
+    private void handleDuplicate(String qName, String value) {
+        if (pageDirective) {
+            if (IMPORT.equalsIgnoreCase(qName)) {
+                // Always merge imports
+                int i = super.getIndex(IMPORT);
+                String v = super.getValue(i);
+                super.setValue(i, v + "," + value);
+                return;
+            } else if (PAGE_ENCODING.equalsIgnoreCase(qName)) {
+                // Page encoding can only occur once per file so a second
+                // attribute - even one with a duplicate value - is an error
+            } else {
+                // Other attributes can be repeated if and only if the values
+                // are identical
+                String v = super.getValue(qName);
+                if (v.equals(value)) {
+                    return;
+                }
+            }
+        }
+
+        // Ordinary tag attributes can't be repeated, even with identical values
+        throw new IllegalArgumentException(
+                    Localizer.getMessage("jsp.error.duplicateqname", qName));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/AsyncFileHandler.java b/bundles/org.apache.tomcat/src/org/apache/juli/AsyncFileHandler.java
new file mode 100644
index 0000000..c58050e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/AsyncFileHandler.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juli;
+
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.LogRecord;
+/**
+ * A {@link FileHandler} implementation that uses a queue of log entries.
+ *
+ * <p>Configuration properties are inherited from the {@link FileHandler}
+ * class. This class does not add its own configuration properties for the
+ * logging configuration, but relies on the following system properties
+ * instead:</p>
+ * 
+ * <ul>
+ *   <li><code>org.apache.juli.AsyncOverflowDropType</code>
+ *    Default value: <code>1</code></li>
+ *   <li><code>org.apache.juli.AsyncMaxRecordCount</code>
+ *    Default value: <code>10000</code></li>
+ *   <li><code>org.apache.juli.AsyncLoggerPollInterval</code>
+ *    Default value: <code>1000</code></li>
+ * </ul>
+ * 
+ * <p>See the System Properties page in the configuration reference of Tomcat.</p>
+ * 
+ * @author Filip Hanik
+ *
+ */
+public class AsyncFileHandler extends FileHandler {
+
+    public static final int OVERFLOW_DROP_LAST = 1;
+    public static final int OVERFLOW_DROP_FIRST = 2;
+    public static final int OVERFLOW_DROP_FLUSH = 3;
+    public static final int OVERFLOW_DROP_CURRENT = 4;
+    
+    public static final int OVERFLOW_DROP_TYPE = Integer.parseInt(System.getProperty("org.apache.juli.AsyncOverflowDropType","1"));
+    public static final int DEFAULT_MAX_RECORDS = Integer.parseInt(System.getProperty("org.apache.juli.AsyncMaxRecordCount","10000"));
+    public static final int LOGGER_SLEEP_TIME = Integer.parseInt(System.getProperty("org.apache.juli.AsyncLoggerPollInterval","1000"));
+   
+    protected static LinkedBlockingDeque<LogEntry> queue = new LinkedBlockingDeque<LogEntry>(DEFAULT_MAX_RECORDS);
+    
+    protected static LoggerThread logger = new LoggerThread();
+    
+    static {
+        logger.start();
+    }
+    
+    protected volatile boolean closed = false;
+    
+    public AsyncFileHandler() {
+        this(null,null,null);
+    }
+
+    public AsyncFileHandler(String directory, String prefix, String suffix) {
+        super(directory, prefix, suffix);
+        open();
+    }
+
+    @Override
+    public void close() {
+        if (closed) return;
+        closed = true;
+        // TODO Auto-generated method stub
+        super.close();
+    }
+    
+    @Override
+    protected void open() {
+        if(!closed) return;
+        closed = false;
+        // TODO Auto-generated method stub
+        super.open();
+    }
+    
+
+    @Override
+    public void publish(LogRecord record) {
+        if (!isLoggable(record)) {
+            return;
+        }
+        LogEntry entry = new LogEntry(record,this);
+        boolean added = false;
+        try {
+            while (!added && !queue.offer(entry)) {
+                switch (OVERFLOW_DROP_TYPE) {
+                    case OVERFLOW_DROP_LAST: {
+                        //remove the last added element
+                        queue.pollLast(); 
+                        break;
+                    }
+                    case OVERFLOW_DROP_FIRST: {
+                        //remove the first element in the queue
+                        queue.pollFirst();
+                        break;
+                    }
+                    case OVERFLOW_DROP_FLUSH: {
+                        added = queue.offer(entry,1000,TimeUnit.MILLISECONDS);
+                        break;
+                    }
+                    case OVERFLOW_DROP_CURRENT: {
+                        added = true;
+                        break;
+                    }
+                }//switch
+            }//while
+        }catch (InterruptedException x) {
+            //allow thread to be interrupted and back out of the publish operation
+            //after this we clear the flag
+            Thread.interrupted();
+        }
+        
+    }
+    
+    protected void publishInternal(LogRecord record) {
+        super.publish(record);
+    }
+
+    protected static class LoggerThread extends Thread {
+        protected boolean run = true;
+        public LoggerThread() {
+            this.setDaemon(true);
+            this.setName("AsyncFileHandlerWriter-"+System.identityHashCode(this));
+        }
+        
+        @Override
+        public void run() {
+            while (run) {
+                try {
+                    LogEntry entry = queue.poll(LOGGER_SLEEP_TIME, TimeUnit.MILLISECONDS);
+                    if (entry!=null) entry.flush();
+                }catch (InterruptedException x) {
+                    Thread.interrupted();
+                }catch (Exception x) {
+                    x.printStackTrace();
+                }
+            }//while
+        }
+    }
+    
+    protected static class LogEntry {
+        private LogRecord record;
+        private AsyncFileHandler handler;
+        public LogEntry(LogRecord record, AsyncFileHandler handler) {
+            super();
+            this.record = record;
+            this.handler = handler;
+        }
+        
+        public boolean flush() {
+            if (handler.closed) {
+                return false;
+            } else {
+                handler.publishInternal(record);
+                return true;
+            }
+        }
+        
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/ClassLoaderLogManager.java b/bundles/org.apache.tomcat/src/org/apache/juli/ClassLoaderLogManager.java
new file mode 100644
index 0000000..d7b9e93
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/ClassLoaderLogManager.java
@@ -0,0 +1,689 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.juli;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLClassLoader;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.WeakHashMap;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+
+/**
+ * Per classloader LogManager implementation.
+ */
+public class ClassLoaderLogManager extends LogManager {
+
+    private final class Cleaner extends Thread {
+        
+        @Override
+        public void run() {
+            if (useShutdownHook) {
+                shutdown();
+            }
+        }
+
+    }
+
+    
+    // ------------------------------------------------------------Constructors
+
+    public ClassLoaderLogManager() {
+        super();
+        try { 
+            Runtime.getRuntime().addShutdownHook(new Cleaner());
+        } catch (IllegalStateException ise) {
+            // We are probably already being shutdown. Ignore this error.
+        }
+    }
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * Map containing the classloader information, keyed per classloader. A
+     * weak hashmap is used to ensure no classloader reference is leaked from 
+     * application redeployment.
+     */
+    protected final Map<ClassLoader, ClassLoaderLogInfo> classLoaderLoggers = 
+        new WeakHashMap<ClassLoader, ClassLoaderLogInfo>();
+
+    
+    /**
+     * This prefix is used to allow using prefixes for the properties names
+     * of handlers and their subcomponents.
+     */
+    protected ThreadLocal<String> prefix = new ThreadLocal<String>();
+
+
+    /**
+     * Determines if the shutdown hook is used to perform any necessary
+     * clean-up such as flushing buffered handlers on JVM shutdown. Defaults to
+     * <code>true</code> but may be set to false if another component ensures
+     * that {@link #shutdown()} is called.
+     */
+    protected volatile boolean useShutdownHook = true;
+
+    
+    // ------------------------------------------------------------- Properties
+
+
+    public boolean isUseShutdownHook() {
+        return useShutdownHook;
+    }
+
+
+    public void setUseShutdownHook(boolean useShutdownHook) {
+        this.useShutdownHook = useShutdownHook;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add the specified logger to the classloader local configuration.
+     * 
+     * @param logger The logger to be added
+     */
+    @Override
+    public synchronized boolean addLogger(final Logger logger) {
+
+        final String loggerName = logger.getName();
+
+        ClassLoader classLoader = 
+            Thread.currentThread().getContextClassLoader();
+        ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
+        if (info.loggers.containsKey(loggerName)) {
+            return false;
+        }
+        info.loggers.put(loggerName, logger);
+
+        // Apply initial level for new logger
+        final String levelString = getProperty(loggerName + ".level");
+        if (levelString != null) {
+            try {
+                AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                    @Override
+                    public Void run() {
+                        logger.setLevel(Level.parse(levelString.trim()));
+                        return null;
+                    }
+                });
+            } catch (IllegalArgumentException e) {
+                // Leave level set to null
+            }
+        }
+
+        // Always instantiate parent loggers so that 
+        // we can control log categories even during runtime
+        int dotIndex = loggerName.lastIndexOf('.');
+        if (dotIndex >= 0) {
+            final String parentName = loggerName.substring(0, dotIndex);
+            Logger.getLogger(parentName);
+        }
+
+        // Find associated node
+        LogNode node = info.rootNode.findNode(loggerName);
+        node.logger = logger;
+
+        // Set parent logger
+        Logger parentLogger = node.findParentLogger();
+        if (parentLogger != null) {
+            doSetParentLogger(logger, parentLogger);
+        }
+
+        // Tell children we are their new parent
+        node.setParentLogger(logger);
+
+        // Add associated handlers, if any are defined using the .handlers property.
+        // In this case, handlers of the parent logger(s) will not be used
+        String handlers = getProperty(loggerName + ".handlers");
+        if (handlers != null) {
+            logger.setUseParentHandlers(false);
+            StringTokenizer tok = new StringTokenizer(handlers, ",");
+            while (tok.hasMoreTokens()) {
+                String handlerName = (tok.nextToken().trim());
+                Handler handler = null;
+                ClassLoader current = classLoader;
+                while (current != null) {
+                    info = classLoaderLoggers.get(current);
+                    if (info != null) {
+                        handler = info.handlers.get(handlerName);
+                        if (handler != null) {
+                            break;
+                        }
+                    }
+                    current = current.getParent();
+                }
+                if (handler != null) {
+                    logger.addHandler(handler);
+                }
+            }
+        }
+
+        // Parse useParentHandlers to set if the logger should delegate to its parent.
+        // Unlike java.util.logging, the default is to not delegate if a list of handlers
+        // has been specified for the logger.
+        String useParentHandlersString = getProperty(loggerName + ".useParentHandlers");
+        if (Boolean.valueOf(useParentHandlersString).booleanValue()) {
+            logger.setUseParentHandlers(true);
+        }
+        
+        return true;
+    }
+
+    
+    /**
+     * Get the logger associated with the specified name inside 
+     * the classloader local configuration. If this returns null,
+     * and the call originated for Logger.getLogger, a new
+     * logger with the specified name will be instantiated and
+     * added using addLogger.
+     * 
+     * @param name The name of the logger to retrieve
+     */
+    @Override
+    public synchronized Logger getLogger(final String name) {
+        ClassLoader classLoader = Thread.currentThread()
+                .getContextClassLoader();
+        return getClassLoaderInfo(classLoader).loggers.get(name);
+    }
+    
+    
+    /**
+     * Get an enumeration of the logger names currently defined in the 
+     * classloader local configuration.
+     */
+    @Override
+    public synchronized Enumeration<String> getLoggerNames() {
+        ClassLoader classLoader = Thread.currentThread()
+                .getContextClassLoader();
+        return Collections.enumeration(getClassLoaderInfo(classLoader).loggers.keySet());
+    }
+
+    
+    /**
+     * Get the value of the specified property in the classloader local
+     * configuration.
+     * 
+     * @param name The property name
+     */    
+    @Override
+    public String getProperty(String name) {
+        ClassLoader classLoader = Thread.currentThread()
+            .getContextClassLoader();
+        String prefix = this.prefix.get();
+        if (prefix != null) {
+            name = prefix + name;
+        }
+        ClassLoaderLogInfo info = getClassLoaderInfo(classLoader);
+        String result = info.props.getProperty(name);
+        // If the property was not found, and the current classloader had no 
+        // configuration (property list is empty), look for the parent classloader
+        // properties.
+        if ((result == null) && (info.props.isEmpty())) {
+            ClassLoader current = classLoader.getParent();
+            while (current != null) {
+                info = classLoaderLoggers.get(current);
+                if (info != null) {
+                    result = info.props.getProperty(name);
+                    if ((result != null) || (!info.props.isEmpty())) {
+                        break;
+                    }
+                }
+                current = current.getParent();
+            }
+            if (result == null) {
+                result = super.getProperty(name);
+            }
+        }
+        // Simple property replacement (mostly for folder names)
+        if (result != null) {
+            result = replace(result);
+        }
+        return result;
+    }
+    
+    
+    @Override
+    public void readConfiguration()
+        throws IOException, SecurityException {
+        
+        checkAccess();
+        
+        readConfiguration(Thread.currentThread().getContextClassLoader());
+        
+    }
+        
+    @Override
+    public void readConfiguration(InputStream is)
+        throws IOException, SecurityException {
+        
+        checkAccess();
+        reset();
+
+        readConfiguration(is, Thread.currentThread().getContextClassLoader());
+    
+    }
+
+    @Override
+    public void reset() throws SecurityException {
+        Thread thread = Thread.currentThread();
+        if (thread.getClass().getName().startsWith(
+                "java.util.logging.LogManager$")) {
+            // Ignore the call from java.util.logging.LogManager.Cleaner,
+            // because we have our own shutdown hook
+            return;
+        }
+        ClassLoader classLoader = thread.getContextClassLoader();
+        ClassLoaderLogInfo clLogInfo = getClassLoaderInfo(classLoader);
+        resetLoggers(clLogInfo);
+        super.reset();
+    }
+
+    /**
+     * Shuts down the logging system.
+     */
+    public void shutdown() {
+        // The JVM is being shutdown. Make sure all loggers for all class
+        // loaders are shutdown
+        for (ClassLoaderLogInfo clLogInfo : classLoaderLoggers.values()) {
+            resetLoggers(clLogInfo);
+        }
+    }
+
+    // -------------------------------------------------------- Private Methods
+    private void resetLoggers(ClassLoaderLogInfo clLogInfo) {
+        // This differs from LogManager#resetLogger() in that we close not all
+        // handlers of all loggers, but only those that are present in our
+        // ClassLoaderLogInfo#handlers list. That is because our #addLogger(..)
+        // method can use handlers from the parent class loaders, and closing
+        // handlers that the current class loader does not own would be not
+        // good.
+        synchronized (clLogInfo) {
+            for (Logger logger : clLogInfo.loggers.values()) {
+                Handler[] handlers = logger.getHandlers();
+                for (Handler handler : handlers) {
+                    logger.removeHandler(handler);
+                }
+            }
+            for (Handler handler : clLogInfo.handlers.values()) {
+                try {
+                    handler.close();
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+            clLogInfo.handlers.clear();
+        }
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Retrieve the configuration associated with the specified classloader. If
+     * it does not exist, it will be created.
+     * 
+     * @param classLoader The classloader for which we will retrieve or build the 
+     *                    configuration
+     */
+    protected ClassLoaderLogInfo getClassLoaderInfo(ClassLoader classLoader) {
+        
+        if (classLoader == null) {
+            classLoader = ClassLoader.getSystemClassLoader();
+        }
+        ClassLoaderLogInfo info = classLoaderLoggers.get(classLoader);
+        if (info == null) {
+            final ClassLoader classLoaderParam = classLoader;
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    try {
+                        readConfiguration(classLoaderParam);
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                    return null;
+                }
+            });
+            info = classLoaderLoggers.get(classLoader);
+        }
+        return info;
+    }
+
+    
+    /**
+     * Read configuration for the specified classloader.
+     * 
+     * @param classLoader 
+     * @throws IOException Error
+     */
+    protected void readConfiguration(ClassLoader classLoader)
+        throws IOException {
+        
+        InputStream is = null;
+        // Special case for URL classloaders which are used in containers: 
+        // only look in the local repositories to avoid redefining loggers 20 times
+        try {
+            if ((classLoader instanceof URLClassLoader) 
+                    && (((URLClassLoader) classLoader).findResource("logging.properties") != null)) {
+                is = classLoader.getResourceAsStream("logging.properties");
+            }
+        } catch (AccessControlException ace) {
+            // No permission to configure logging in context
+            // Log and carry on
+            ClassLoaderLogInfo info = classLoaderLoggers.get(ClassLoader.getSystemClassLoader());
+            if (info != null) {
+                Logger log = info.loggers.get("");
+                if (log != null) {
+                    Permission perm = ace.getPermission();
+                    if (perm instanceof FilePermission && perm.getActions().equals("read")) {
+                        log.warning("Reading " + perm.getName() + " is not permitted. See \"per context logging\" in the default catalina.policy file.");
+                    }
+                    else {
+                        log.warning("Reading logging.properties is not permitted in some context. See \"per context logging\" in the default catalina.policy file.");
+                        log.warning("Original error was: " + ace.getMessage());
+                    }
+                }
+            }
+        }
+        if ((is == null) && (classLoader == ClassLoader.getSystemClassLoader())) {
+            String configFileStr = System.getProperty("java.util.logging.config.file");
+            if (configFileStr != null) {
+                try {
+                    is = new FileInputStream(replace(configFileStr));
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+            // Try the default JVM configuration
+            if (is == null) {
+                File defaultFile = new File(new File(System.getProperty("java.home"), "lib"), 
+                    "logging.properties");
+                try {
+                    is = new FileInputStream(defaultFile);
+                } catch (IOException e) {
+                    // Critical problem, do something ...
+                }
+            }
+        }
+        
+        Logger localRootLogger = new RootLogger();
+        if (is == null) {
+            // Retrieve the root logger of the parent classloader instead
+            ClassLoader current = classLoader.getParent();
+            ClassLoaderLogInfo info = null;
+            while (current != null && info == null) {
+                info = getClassLoaderInfo(current);
+                current = current.getParent();
+            }
+            if (info != null) {
+                localRootLogger.setParent(info.rootNode.logger);
+            }
+        }
+        ClassLoaderLogInfo info = 
+            new ClassLoaderLogInfo(new LogNode(null, localRootLogger));
+        classLoaderLoggers.put(classLoader, info);
+        
+        if (is != null) {
+            readConfiguration(is, classLoader);
+        }
+        addLogger(localRootLogger);
+        
+    }
+    
+    
+    /**
+     * Load specified configuration.
+     * 
+     * @param is InputStream to the properties file
+     * @param classLoader for which the configuration will be loaded
+     * @throws IOException If something wrong happens during loading
+     */
+    protected void readConfiguration(InputStream is, ClassLoader classLoader)
+        throws IOException {
+        
+        ClassLoaderLogInfo info = classLoaderLoggers.get(classLoader);
+        
+        try {
+            info.props.load(is);
+        } catch (IOException e) {
+            // Report error
+            System.err.println("Configuration error");
+            e.printStackTrace();
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ioe) {
+                // Ignore
+            }
+        }
+        
+        // Create handlers for the root logger of this classloader
+        String rootHandlers = info.props.getProperty(".handlers");
+        String handlers = info.props.getProperty("handlers");
+        Logger localRootLogger = info.rootNode.logger;
+        if (handlers != null) {
+            StringTokenizer tok = new StringTokenizer(handlers, ",");
+            while (tok.hasMoreTokens()) {
+                String handlerName = (tok.nextToken().trim());
+                String handlerClassName = handlerName;
+                String prefix = "";
+                if (handlerClassName.length() <= 0) {
+                    continue;
+                }
+                // Parse and remove a prefix (prefix start with a digit, such as 
+                // "10WebappFooHanlder.")
+                if (Character.isDigit(handlerClassName.charAt(0))) {
+                    int pos = handlerClassName.indexOf('.');
+                    if (pos >= 0) {
+                        prefix = handlerClassName.substring(0, pos + 1);
+                        handlerClassName = handlerClassName.substring(pos + 1);
+                    }
+                }
+                try {
+                    this.prefix.set(prefix);
+                    Handler handler = 
+                        (Handler) classLoader.loadClass(handlerClassName).newInstance();
+                    // The specification strongly implies all configuration should be done 
+                    // during the creation of the handler object.
+                    // This includes setting level, filter, formatter and encoding.
+                    this.prefix.set(null);
+                    info.handlers.put(handlerName, handler);
+                    if (rootHandlers == null) {
+                        localRootLogger.addHandler(handler);
+                    }
+                } catch (Exception e) {
+                    // Report error
+                    System.err.println("Handler error");
+                    e.printStackTrace();
+                }
+            }
+            
+        }
+        
+    }
+    
+    
+    /**
+     * Set parent child relationship between the two specified loggers.
+     * 
+     * @param logger
+     * @param parent
+     */
+    protected static void doSetParentLogger(final Logger logger,
+            final Logger parent) {
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            @Override
+            public Void run() {
+                logger.setParent(parent);
+                return null;
+            }
+        });
+    }
+
+    
+    /**
+     * System property replacement in the given string.
+     * 
+     * @param str The original string
+     * @return the modified string
+     */
+    protected String replace(String str) {
+        String result = str;
+        int pos_start = result.indexOf("${");
+        if (pos_start != -1) {
+            int pos_end = result.indexOf('}');
+            if (pos_end != -1) {
+                String propName = result.substring(pos_start + 2, pos_end);
+                String replacement = System.getProperty(propName);
+                if (replacement != null) {
+                    if(pos_start >0) {
+                        result = result.substring(0,pos_start) + 
+                            replacement + replace(result.substring(pos_end + 1));
+                    } else {                       
+                        result = replacement + replace(result.substring(pos_end + 1));
+                    }
+                }
+            }
+        }
+        return result;
+    }
+    
+
+    // ---------------------------------------------------- LogNode Inner Class
+
+
+    protected static final class LogNode {
+        Logger logger;
+
+        protected final Map<String, LogNode> children = 
+            new HashMap<String, LogNode>();
+
+        protected final LogNode parent;
+
+        LogNode(final LogNode parent, final Logger logger) {
+            this.parent = parent;
+            this.logger = logger;
+        }
+
+        LogNode(final LogNode parent) {
+            this(parent, null);
+        }
+
+        LogNode findNode(String name) {
+            LogNode currentNode = this;
+            if (logger.getName().equals(name)) {
+                return this;
+            }
+            while (name != null) {
+                final int dotIndex = name.indexOf('.');
+                final String nextName;
+                if (dotIndex < 0) {
+                    nextName = name;
+                    name = null;
+                } else {
+                    nextName = name.substring(0, dotIndex);
+                    name = name.substring(dotIndex + 1);
+                }
+                LogNode childNode = currentNode.children.get(nextName);
+                if (childNode == null) {
+                    childNode = new LogNode(currentNode);
+                    currentNode.children.put(nextName, childNode);
+                }
+                currentNode = childNode;
+            }
+            return currentNode;
+        }
+
+        Logger findParentLogger() {
+            Logger logger = null;
+            LogNode node = parent;
+            while (node != null && logger == null) {
+                logger = node.logger;
+                node = node.parent;
+            }
+            return logger;
+        }
+
+        void setParentLogger(final Logger parent) {
+            for (final Iterator<LogNode> iter =
+                children.values().iterator(); iter.hasNext();) {
+                final LogNode childNode = iter.next();
+                if (childNode.logger == null) {
+                    childNode.setParentLogger(parent);
+                } else {
+                    doSetParentLogger(childNode.logger, parent);
+                }
+            }
+        }
+
+    }
+
+
+    // -------------------------------------------- ClassLoaderInfo Inner Class
+
+
+    protected static final class ClassLoaderLogInfo {
+        final LogNode rootNode;
+        final Map<String, Logger> loggers = new HashMap<String, Logger>();
+        final Map<String, Handler> handlers = new HashMap<String, Handler>();
+        final Properties props = new Properties();
+
+        ClassLoaderLogInfo(final LogNode rootNode) {
+            this.rootNode = rootNode;
+        }
+
+    }
+
+
+    // ------------------------------------------------- RootLogger Inner Class
+
+
+    /**
+     * This class is needed to instantiate the root of each per classloader 
+     * hierarchy.
+     */
+    protected static class RootLogger extends Logger {
+        public RootLogger() {
+            super("", null);
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/FileHandler.java b/bundles/org.apache.tomcat/src/org/apache/juli/FileHandler.java
new file mode 100644
index 0000000..07b69ad
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/FileHandler.java
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.juli;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.sql.Timestamp;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.ErrorManager;
+import java.util.logging.Filter;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * Implementation of <b>Handler</b> that appends log messages to a file
+ * named {prefix}{date}{suffix} in a configured directory.
+ *
+ * <p>The following configuration properties are available:</p>
+ * 
+ * <ul>
+ *   <li><code>directory</code> - The directory where to create the log file.
+ *    If the path is not absolute, it is relative to the current working
+ *    directory of the application. The Apache Tomcat configuration files usually
+ *    specify an absolute path for this property,
+ *    <code>${catalina.base}/logs</code> 
+ *    Default value: <code>logs</code></li>
+ *   <li><code>rotatable</code> - If <code>true</code>, the log file will be
+ *    rotated on the first write past midnight and the filename will be
+ *    <code>{prefix}{date}{suffix}</code>, where date is yyyy-MM-dd. If <code>false</code>,
+ *    the file will not be rotated and the filename will be <code>{prefix}{suffix}</code>.
+ *    Default value: <code>true</code></li>
+ *   <li><code>prefix</code> - The leading part of the log file name.
+ *    Default value: <code>juli.</code></li>
+ *   <li><code>suffix</code> - The trailing part of the log file name. Default value: <code>.log</code></li>
+ *   <li><code>bufferSize</code> - Configures buffering. The value of <code>0</code>
+ *    uses system default buffering (typically an 8K buffer will be used). A
+ *    value of <code>&lt;0</code> forces a writer flush upon each log write. A
+ *    value <code>&gt;0</code> uses a BufferedOutputStream with the defined
+ *    value but note that the system default buffering will also be
+ *    applied. Default value: <code>-1</code></li>
+ *   <li><code>encoding</code> - Character set used by the log file. Default value:
+ *    empty string, which means to use the system default character set.</li>
+ *   <li><code>level</code> - The level threshold for this Handler. See the
+ *    <code>java.util.logging.Level</code> class for the possible levels.
+ *    Default value: <code>ALL</code></li>
+ *   <li><code>filter</code> - The <code>java.util.logging.Filter</code>
+ *    implementation class name for this Handler. Default value: unset</li>
+ *   <li><code>formatter</code> - The <code>java.util.logging.Formatter</code>
+ *    implementation class name for this Handler. Default value:
+ *    <code>java.util.logging.SimpleFormatter</code></li>
+ * </ul>
+ *
+ * @version $Id: FileHandler.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+
+public class FileHandler
+    extends Handler {
+
+
+    // ------------------------------------------------------------ Constructor
+
+    
+    public FileHandler() {
+        this(null, null, null);
+    }
+    
+    
+    public FileHandler(String directory, String prefix, String suffix) {
+        this.directory = directory;
+        this.prefix = prefix;
+        this.suffix = suffix;
+        configure();
+        openWriter();
+    }
+    
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The as-of date for the currently open log file, or a zero-length
+     * string if there is no open log file.
+     */
+    private volatile String date = "";
+
+
+    /**
+     * The directory in which log files are created.
+     */
+    private String directory = null;
+
+
+    /**
+     * The prefix that is added to log file filenames.
+     */
+    private String prefix = null;
+
+
+    /**
+     * The suffix that is added to log file filenames.
+     */
+    private String suffix = null;
+
+
+    /**
+     * Determines whether the logfile is rotatable
+     */
+    private boolean rotatable = true;
+
+
+    /**
+     * The PrintWriter to which we are currently logging, if any.
+     */
+    private volatile PrintWriter writer = null;
+
+
+    /**
+     * Lock used to control access to the writer.
+     */
+    protected ReadWriteLock writerLock = new ReentrantReadWriteLock();
+
+
+    /**
+     * Log buffer size.
+     */
+    private int bufferSize = -1;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Format and publish a <tt>LogRecord</tt>.
+     *
+     * @param  record  description of the log event
+     */
+    @Override
+    public void publish(LogRecord record) {
+
+        if (!isLoggable(record)) {
+            return;
+        }
+
+        // Construct the timestamp we will use, if requested
+        Timestamp ts = new Timestamp(System.currentTimeMillis());
+        String tsString = ts.toString().substring(0, 19);
+        String tsDate = tsString.substring(0, 10);
+
+        writerLock.readLock().lock();
+        // If the date has changed, switch log files
+        if (rotatable && !date.equals(tsDate)) {
+            // Update to writeLock before we switch
+            writerLock.readLock().unlock();
+            writerLock.writeLock().lock();
+            try {
+                // Make sure another thread hasn't already done this
+                if (!date.equals(tsDate)) {
+                    closeWriter();
+                    date = tsDate;
+                    openWriter();
+                }
+                // Down grade to read-lock. This ensures the writer remains valid
+                // until the log message is written
+                writerLock.readLock().lock();
+            } finally {
+                writerLock.writeLock().unlock();
+            }
+        }
+
+        try {
+            String result = null;
+            try {
+                result = getFormatter().format(record);
+            } catch (Exception e) {
+                reportError(null, e, ErrorManager.FORMAT_FAILURE);
+                return;
+            }
+
+            try {
+                if (writer!=null) {
+                    writer.write(result);
+                    if (bufferSize < 0) {
+                        writer.flush();
+                    }
+                } else {
+                    reportError("FileHandler is closed or not yet initialized, unable to log ["+result+"]", null, ErrorManager.WRITE_FAILURE);
+                }
+            } catch (Exception e) {
+                reportError(null, e, ErrorManager.WRITE_FAILURE);
+                return;
+            }
+        } finally {
+            writerLock.readLock().unlock();
+        }
+    }
+    
+    
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Close the currently open log file (if any).
+     */
+    @Override
+    public void close() {
+        closeWriter();
+    }
+
+    protected void closeWriter() {
+        
+        writerLock.writeLock().lock();
+        try {
+            if (writer == null)
+                return;
+            writer.write(getFormatter().getTail(this));
+            writer.flush();
+            writer.close();
+            writer = null;
+            date = "";
+        } catch (Exception e) {
+            reportError(null, e, ErrorManager.CLOSE_FAILURE);
+        } finally {
+            writerLock.writeLock().unlock();
+        }
+    }
+
+
+    /**
+     * Flush the writer.
+     */
+    @Override
+    public void flush() {
+
+        writerLock.readLock().lock();
+        try {
+            if (writer == null)
+                return;
+            writer.flush();
+        } catch (Exception e) {
+            reportError(null, e, ErrorManager.FLUSH_FAILURE);
+        } finally {
+            writerLock.readLock().unlock();
+        }
+        
+    }
+    
+    
+    /**
+     * Configure from <code>LogManager</code> properties.
+     */
+    private void configure() {
+
+        Timestamp ts = new Timestamp(System.currentTimeMillis());
+        String tsString = ts.toString().substring(0, 19);
+        date = tsString.substring(0, 10);
+
+        String className = this.getClass().getName(); //allow classes to override
+        
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        
+        // Retrieve configuration of logging file name
+        rotatable = Boolean.parseBoolean(getProperty(className + ".rotatable", "true"));
+        if (directory == null)
+            directory = getProperty(className + ".directory", "logs");
+        if (prefix == null)
+            prefix = getProperty(className + ".prefix", "juli.");
+        if (suffix == null)
+            suffix = getProperty(className + ".suffix", ".log");
+        String sBufferSize = getProperty(className + ".bufferSize", String.valueOf(bufferSize));
+        try {
+            bufferSize = Integer.parseInt(sBufferSize);
+        } catch (NumberFormatException ignore) {
+            //no op
+        }
+        // Get encoding for the logging file
+        String encoding = getProperty(className + ".encoding", null);
+        if (encoding != null && encoding.length() > 0) {
+            try {
+                setEncoding(encoding);
+            } catch (UnsupportedEncodingException ex) {
+                // Ignore
+            }
+        }
+
+        // Get logging level for the handler
+        setLevel(Level.parse(getProperty(className + ".level", "" + Level.ALL)));
+
+        // Get filter configuration
+        String filterName = getProperty(className + ".filter", null);
+        if (filterName != null) {
+            try {
+                setFilter((Filter) cl.loadClass(filterName).newInstance());
+            } catch (Exception e) {
+                // Ignore
+            }
+        }
+
+        // Set formatter
+        String formatterName = getProperty(className + ".formatter", null);
+        if (formatterName != null) {
+            try {
+                setFormatter((Formatter) cl.loadClass(formatterName).newInstance());
+            } catch (Exception e) {
+                // Ignore
+            }
+        } else {
+            setFormatter(new SimpleFormatter());
+        }
+        
+        // Set error manager
+        setErrorManager(new ErrorManager());
+        
+    }
+
+    
+    private String getProperty(String name, String defaultValue) {
+        String value = LogManager.getLogManager().getProperty(name);
+        if (value == null) {
+            value = defaultValue;
+        } else {
+            value = value.trim();
+        }
+        return value;
+    }
+    
+    
+    /**
+     * Open the new log file for the date specified by <code>date</code>.
+     */
+    protected void open() {
+        openWriter();
+    }
+    
+    protected void openWriter() {
+
+        // Create the directory if necessary
+        File dir = new File(directory);
+        dir.mkdirs();
+
+        // Open the current log file
+        writerLock.writeLock().lock();
+        try {
+            String pathname = dir.getAbsolutePath() + File.separator +
+                prefix + (rotatable ? date : "") + suffix;
+            String encoding = getEncoding();
+            FileOutputStream fos = new FileOutputStream(pathname, true);
+            OutputStream os = bufferSize>0?new BufferedOutputStream(fos,bufferSize):fos;
+            writer = new PrintWriter(
+                    (encoding != null) ? new OutputStreamWriter(os, encoding)
+                                       : new OutputStreamWriter(os), false);
+            writer.write(getFormatter().getHead(this));
+        } catch (Exception e) {
+            reportError(null, e, ErrorManager.OPEN_FAILURE);
+            writer = null;
+        } finally {
+            writerLock.writeLock().unlock();
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/JdkLoggerFormatter.java b/bundles/org.apache.tomcat/src/org/apache/juli/JdkLoggerFormatter.java
new file mode 100644
index 0000000..75af095
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/JdkLoggerFormatter.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.juli;
+
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+
+
+/**
+ * A more compact formatter. 
+ * 
+ * Equivalent log4j config:
+ *   <pre>
+ *  log4j.rootCategory=WARN, A1   
+ *  log4j.appender.A1=org.apache.log4j.ConsoleAppender
+ *  log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+ *  log4j.appender.A1.Target=System.err
+ *  log4j.appender.A1.layout.ConversionPattern=%r %-15.15c{2} %-1.1p %m %n
+ *  </pre>
+ *
+ * Example:
+ *  1130122891846 Http11BaseProtocol I Initializing Coyote HTTP/1.1 on http-8800
+ *  
+ * 
+ * @author Costin Manolache
+ */
+public class JdkLoggerFormatter extends Formatter {
+    // values from JDK Level
+    public static final int LOG_LEVEL_TRACE  = 400;
+    public static final int LOG_LEVEL_DEBUG  = 500;
+    public static final int LOG_LEVEL_INFO   = 800;
+    public static final int LOG_LEVEL_WARN   = 900;
+    public static final int LOG_LEVEL_ERROR  = 1000;
+    public static final int LOG_LEVEL_FATAL  = 1000;
+
+    @Override
+    public String format(LogRecord record) {
+        Throwable t=record.getThrown();
+        int level=record.getLevel().intValue();
+        String name=record.getLoggerName();
+        long time=record.getMillis();
+        String message=formatMessage(record);
+
+        
+        if( name.indexOf(".") >= 0 ) 
+            name = name.substring(name.lastIndexOf(".") + 1);
+
+        // Use a string buffer for better performance
+        StringBuilder buf = new StringBuilder();
+        
+        buf.append(time);
+        buf.append(" ");
+        
+        // pad to 8 to make it more readable 
+        for( int i=0; i<8-buf.length(); i++ ) { buf.append(" "); }
+        
+        //      Append a readable representation of the log level.
+        switch(level) {
+         case LOG_LEVEL_TRACE: buf.append(" T "); break;
+         case LOG_LEVEL_DEBUG: buf.append(" D "); break;
+         case LOG_LEVEL_INFO:  buf.append(" I ");  break;
+         case LOG_LEVEL_WARN:  buf.append(" W ");  break;
+         case LOG_LEVEL_ERROR: buf.append(" E "); break;
+         //case : buf.append(" F "); break;
+         default: buf.append("   ");
+         }
+         
+
+        // Append the name of the log instance if so configured
+        buf.append(name);
+        
+        // pad to 20 chars 
+        for( int i=0; i<8-buf.length(); i++ ) { buf.append(" "); }
+                
+        // Append the message
+        buf.append(message);
+        
+        // Append stack trace if not null
+        if(t != null) {
+            buf.append(" \n");
+            
+            java.io.StringWriter sw= new java.io.StringWriter(1024);
+            java.io.PrintWriter pw= new java.io.PrintWriter(sw);
+            t.printStackTrace(pw);
+            pw.close();
+            buf.append(sw.toString());
+        }
+        
+        buf.append("\n");
+        // Print to the appropriate destination
+        return buf.toString();
+    }
+    
+} 
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/OneLineFormatter.java b/bundles/org.apache.tomcat/src/org/apache/juli/OneLineFormatter.java
new file mode 100644
index 0000000..f7e32c4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/OneLineFormatter.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.juli;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+/**
+ * Provides same information as default log format but on a single line to make
+ * it easier to grep the logs. The only exception is stacktraces which are
+ * always preceded by whitespace to make it simple to skip them.
+ */
+/*
+ * Date processing based on AccessLogValve.
+ */
+public class OneLineFormatter extends Formatter {
+
+    /**
+     * The set of month abbreviations for log messages.
+     */
+    private static final String months[] = {"Jan", "Feb", "Mar", "Apr", "May",
+        "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+    private static final String LINE_SEP = System.getProperty("line.separator");
+    private static final String ST_SEP = LINE_SEP + " ";
+
+    private final SimpleDateFormat dayFormatter = new SimpleDateFormat("dd");
+    private final SimpleDateFormat monthFormatter = new SimpleDateFormat("MM");
+    private final SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy");
+    private final SimpleDateFormat timeFormatter =
+        new SimpleDateFormat("HH:mm:ss.S");
+    
+    private Date currentDate;
+    private String currentDateString;
+
+    @Override
+    public String format(LogRecord record) {
+        StringBuilder sb = new StringBuilder();
+        
+        // Timestamp
+        addTimestamp(sb, new Date(record.getMillis()));
+        
+        // Severity
+        sb.append(' ');
+        sb.append(record.getLevel());
+        
+        // Source
+        sb.append(' ');
+        sb.append(record.getSourceClassName());
+        sb.append('.');
+        sb.append(record.getSourceMethodName());
+        
+        // Message
+        sb.append(' ');
+        sb.append(record.getMessage());
+        
+        // Stack trace
+        if (record.getThrown() != null) {
+            sb.append(ST_SEP);
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            record.getThrown().printStackTrace(pw);
+            pw.close();
+            sb.append(sw.getBuffer());
+        }
+
+        // New line for next record
+        sb.append(LINE_SEP);
+
+        return sb.toString();
+    }
+
+    public void addTimestamp(StringBuilder buf, Date date) {
+        if (currentDate != date) {
+            synchronized (this) {
+                if (currentDate != date) {
+                    StringBuilder current = new StringBuilder(32);
+                    current.append(dayFormatter.format(date)); // Day
+                    current.append('-');
+                    current.append(lookup(monthFormatter.format(date))); // Month
+                    current.append('-');
+                    current.append(yearFormatter.format(date)); // Year
+                    current.append(' ');
+                    current.append(timeFormatter.format(date)); // Time
+                    currentDateString = current.toString();
+                    currentDate = date;
+                }
+            }
+        }
+        buf.append(currentDateString);
+    }
+
+    /**
+     * Return the month abbreviation for the specified month, which must
+     * be a two-digit String.
+     *
+     * @param month Month number ("01" .. "12").
+     */
+    private String lookup(String month) {
+        int index;
+        try {
+            index = Integer.parseInt(month) - 1;
+        } catch (Exception e) {
+            index = 0;  // Can not happen, in theory
+        }
+        return (months[index]);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/VerbatimFormatter.java b/bundles/org.apache.tomcat/src/org/apache/juli/VerbatimFormatter.java
new file mode 100644
index 0000000..aff4f45
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/VerbatimFormatter.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.juli;
+
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+/**
+ * Outputs the just the log message with no additional elements. Stack traces
+ * are not logged. Log messages are separated by
+ * <code>System.getProperty("line.separator")</code>. This is intended for use
+ * by access logs and the like that need complete control over the output
+ * format.
+ */
+public class VerbatimFormatter extends Formatter {
+
+    private static final String LINE_SEP = System.getProperty("line.separator");
+
+    @Override
+    public String format(LogRecord record) {
+        // Timestamp
+        StringBuilder sb = new StringBuilder(record.getMessage());
+        
+        // New line for next record
+        sb.append(LINE_SEP);
+
+        return sb.toString();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/BeanFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/BeanFactory.java
new file mode 100644
index 0000000..b5bc69b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/BeanFactory.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.naming.factory;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.ResourceRef;
+
+/**
+ * Object factory for any Resource conforming to the JavaBean spec.
+ * 
+ * <p>This factory can be configured in a <code>&lt;Context&gt;</code> element
+ * in your <code>conf/server.xml</code>
+ * configuration file.  An example of factory configuration is:</p>
+ * <pre>
+ * &lt;Resource name="jdbc/myDataSource" auth="SERVLET"
+ *   type="oracle.jdbc.pool.OracleConnectionCacheImpl"/&gt;
+ * &lt;ResourceParams name="jdbc/myDataSource"&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;factory&lt;/name&gt;
+ *     &lt;value&gt;org.apache.naming.factory.BeanFactory&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;driverType&lt;/name&gt;
+ *     &lt;value&gt;thin&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;serverName&lt;/name&gt;
+ *     &lt;value&gt;hue&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;networkProtocol&lt;/name&gt;
+ *     &lt;value&gt;tcp&lt;/value&gt;
+ *   &lt;/parameter&gt; 
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;databaseName&lt;/name&gt;
+ *     &lt;value&gt;XXXX&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;portNumber&lt;/name&gt;
+ *     &lt;value&gt;NNNN&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;user&lt;/name&gt;
+ *     &lt;value&gt;XXXX&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;password&lt;/name&gt;
+ *     &lt;value&gt;XXXX&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;maxLimit&lt;/name&gt;
+ *     &lt;value&gt;5&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ * &lt;/ResourceParams&gt;
+ * </pre>
+ *
+ * @author <a href="mailto:aner at ncstech.com">Aner Perez</a>
+ */
+public class BeanFactory
+    implements ObjectFactory {
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Create a new Bean instance.
+     * 
+     * @param obj The reference object describing the Bean
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws NamingException {
+
+        if (obj instanceof ResourceRef) {
+
+            try {
+                
+                Reference ref = (Reference) obj;
+                String beanClassName = ref.getClassName();
+                Class<?> beanClass = null;
+                ClassLoader tcl = 
+                    Thread.currentThread().getContextClassLoader();
+                if (tcl != null) {
+                    try {
+                        beanClass = tcl.loadClass(beanClassName);
+                    } catch(ClassNotFoundException e) {
+                    }
+                } else {
+                    try {
+                        beanClass = Class.forName(beanClassName);
+                    } catch(ClassNotFoundException e) {
+                        e.printStackTrace();
+                    }
+                }
+                if (beanClass == null) {
+                    throw new NamingException
+                        ("Class not found: " + beanClassName);
+                }
+                
+                BeanInfo bi = Introspector.getBeanInfo(beanClass);
+                PropertyDescriptor[] pda = bi.getPropertyDescriptors();
+                
+                Object bean = beanClass.newInstance();
+                
+                Enumeration<RefAddr> e = ref.getAll();
+                while (e.hasMoreElements()) {
+                    
+                    RefAddr ra = e.nextElement();
+                    String propName = ra.getType();
+                    
+                    if (propName.equals(Constants.FACTORY) ||
+                        propName.equals("scope") || propName.equals("auth") ||
+                        propName.equals("singleton")) {
+                        continue;
+                    }
+                    
+                    String value = (String)ra.getContent();
+                    
+                    Object[] valueArray = new Object[1];
+                    
+                    int i = 0;
+                    for (i = 0; i<pda.length; i++) {
+
+                        if (pda[i].getName().equals(propName)) {
+
+                            Class<?> propType = pda[i].getPropertyType();
+
+                            if (propType.equals(String.class)) {
+                                valueArray[0] = value;
+                            } else if (propType.equals(Character.class) 
+                                       || propType.equals(char.class)) {
+                                valueArray[0] =
+                                    Character.valueOf(value.charAt(0));
+                            } else if (propType.equals(Byte.class) 
+                                       || propType.equals(byte.class)) {
+                                valueArray[0] = new Byte(value);
+                            } else if (propType.equals(Short.class) 
+                                       || propType.equals(short.class)) {
+                                valueArray[0] = new Short(value);
+                            } else if (propType.equals(Integer.class) 
+                                       || propType.equals(int.class)) {
+                                valueArray[0] = new Integer(value);
+                            } else if (propType.equals(Long.class) 
+                                       || propType.equals(long.class)) {
+                                valueArray[0] = new Long(value);
+                            } else if (propType.equals(Float.class) 
+                                       || propType.equals(float.class)) {
+                                valueArray[0] = new Float(value);
+                            } else if (propType.equals(Double.class) 
+                                       || propType.equals(double.class)) {
+                                valueArray[0] = new Double(value);
+                            } else if (propType.equals(Boolean.class)
+                                       || propType.equals(boolean.class)) {
+                                valueArray[0] = Boolean.valueOf(value);
+                            } else {
+                                throw new NamingException
+                                    ("String conversion for property type '"
+                                     + propType.getName() + "' not available");
+                            }
+                            
+                            Method setProp = pda[i].getWriteMethod();
+                            if (setProp != null) {
+                                setProp.invoke(bean, valueArray);
+                            } else {
+                                throw new NamingException
+                                    ("Write not allowed for property: " 
+                                     + propName);
+                            }
+
+                            break;
+
+                        }
+
+                    }
+
+                    if (i == pda.length) {
+                        throw new NamingException
+                            ("No set method found for property: " + propName);
+                    }
+
+                }
+
+                return bean;
+
+            } catch (java.beans.IntrospectionException ie) {
+                NamingException ne = new NamingException(ie.getMessage());
+                ne.setRootCause(ie);
+                throw ne;
+            } catch (java.lang.IllegalAccessException iae) {
+                NamingException ne = new NamingException(iae.getMessage());
+                ne.setRootCause(iae);
+                throw ne;
+            } catch (java.lang.InstantiationException ie2) {
+                NamingException ne = new NamingException(ie2.getMessage());
+                ne.setRootCause(ie2);
+                throw ne;
+            } catch (java.lang.reflect.InvocationTargetException ite) {
+                NamingException ne = new NamingException(ite.getMessage());
+                ne.setRootCause(ite);
+                throw ne;
+            }
+
+        } else {
+            return null;
+        }
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/Constants.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/Constants.java
new file mode 100644
index 0000000..a9fcf86
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/Constants.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+
+/**
+ * Static constants for this package.
+ */
+
+public final class Constants {
+
+    public static final String Package = "org.apache.naming.factory";
+
+    public static final String DEFAULT_RESOURCE_FACTORY = 
+        Package + ".ResourceFactory";
+
+    public static final String DEFAULT_RESOURCE_LINK_FACTORY = 
+        Package + ".ResourceLinkFactory";
+
+    public static final String DEFAULT_TRANSACTION_FACTORY = 
+        Package + ".TransactionFactory";
+
+    public static final String DEFAULT_RESOURCE_ENV_FACTORY = 
+        Package + ".ResourceEnvFactory";
+
+    public static final String DEFAULT_EJB_FACTORY = 
+        Package + ".EjbFactory";
+
+    public static final String DEFAULT_SERVICE_FACTORY = 
+        Package + ".webservices.ServiceRefFactory";
+
+    public static final String DEFAULT_HANDLER_FACTORY = 
+        Package + ".HandlerFactory";
+
+    public static final String DBCP_DATASOURCE_FACTORY = 
+        "org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory";
+
+    public static final String OPENEJB_EJB_FACTORY = 
+        Package + ".OpenEjbFactory";
+
+    public static final String FACTORY = "factory";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/DataSourceLinkFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/DataSourceLinkFactory.java
new file mode 100644
index 0000000..fe0f7fa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/DataSourceLinkFactory.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.SQLException;
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.sql.DataSource;
+
+
+
+/**
+ * <p>Object factory for resource links for shared data sources.</p>
+ * 
+ * @author Filip Hanik
+ */
+public class DataSourceLinkFactory extends ResourceLinkFactory {
+
+    public static void setGlobalContext(Context newGlobalContext) {
+        ResourceLinkFactory.setGlobalContext(newGlobalContext);
+    }
+    // ------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Create a new DataSource instance.
+     * 
+     * @param obj The reference object describing the DataSource
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?,?> environment)
+        throws NamingException {
+        Object result = super.getObjectInstance(obj, name, nameCtx, environment);
+        // Can we process this request?
+        if (result!=null) {
+            Reference ref = (Reference) obj;
+            RefAddr userAttr = ref.get("username");
+            RefAddr passAttr = ref.get("password");
+            if (userAttr.getContent()!=null && passAttr.getContent()!=null) {
+                result = wrapDataSource(result,userAttr.getContent().toString(), passAttr.getContent().toString());
+            }
+        }
+        return result;
+    }
+    
+    protected Object wrapDataSource(Object datasource, String username, String password) throws NamingException {
+        try {
+            Class<?> proxyClass = Proxy.getProxyClass(datasource.getClass().getClassLoader(), datasource.getClass().getInterfaces());
+            Constructor<?> proxyConstructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
+            DataSourceHandler handler = new DataSourceHandler((DataSource)datasource, username, password);
+            return proxyConstructor.newInstance(handler);    
+        }catch (Exception x) {
+            if (x instanceof NamingException) throw (NamingException)x;
+            else {
+                NamingException nx = new NamingException(x.getMessage());
+                nx.initCause(x);
+                throw nx;
+            }
+        }
+    }
+    
+    /**
+     * Simple wrapper class that will allow a user to configure a ResourceLink for a data source
+     * so that when {@link javax.sql.DataSource#getConnection()} is called, it will invoke 
+     * {@link javax.sql.DataSource#getConnection(String, String)} with the preconfigured username and password.
+     */
+    public static class DataSourceHandler implements InvocationHandler {
+        private final DataSource ds; 
+        private final String username; 
+        private final String password;
+        private final Method getConnection;
+        public DataSourceHandler(DataSource ds, String username, String password) throws Exception {
+            this.ds = ds;
+            this.username = username;
+            this.password = password;
+            getConnection = ds.getClass().getMethod("getConnection", String.class, String.class);
+        }
+        
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            
+            if ("getConnection".equals(method.getName()) && (args==null || args.length==0)) {
+                args = new String[] {username,password};
+                method = getConnection;
+            } else if ("unwrap".equals(method.getName())) {
+                return unwrap((Class<?>)args[0]);
+            }
+            try {
+                return method.invoke(ds,args);
+            }catch (Throwable t) {
+                if (t instanceof InvocationTargetException) {
+                    InvocationTargetException it = (InvocationTargetException)t;
+                    throw it.getCause()!=null?it.getCause():it;
+                } else {
+                    throw t;
+                }
+            }
+        }
+        
+        public Object unwrap(Class<?> iface) throws SQLException {
+            if (iface == DataSource.class) {
+                return ds;
+            } else {
+                throw new SQLException("Not a wrapper of "+iface.getName());
+            }
+        }
+        
+    }
+    
+    
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/EjbFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/EjbFactory.java
new file mode 100644
index 0000000..100c4e1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/EjbFactory.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.EjbRef;
+
+/**
+ * Object factory for EJBs.
+ * 
+ * @author Remy Maucherat
+ * @version $Id: EjbFactory.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+
+public class EjbFactory
+    implements ObjectFactory {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Create a new EJB instance.
+     * 
+     * @param obj The reference object describing the DataSource
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws Exception {
+        
+        if (obj instanceof EjbRef) {
+            Reference ref = (Reference) obj;
+
+            // If ejb-link has been specified, resolving the link using JNDI
+            RefAddr linkRefAddr = ref.get(EjbRef.LINK);
+            if (linkRefAddr != null) {
+                // Retrieving the EJB link
+                String ejbLink = linkRefAddr.getContent().toString();
+                Object beanObj = (new InitialContext()).lookup(ejbLink);
+                // Load home interface and checking if bean correctly
+                // implements specified home interface
+                /*
+                String homeClassName = ref.getClassName();
+                try {
+                    Class home = Class.forName(homeClassName);
+                    if (home.isInstance(beanObj)) {
+                        System.out.println("Bean of type " 
+                                           + beanObj.getClass().getName() 
+                                           + " implements home interface " 
+                                           + home.getName());
+                    } else {
+                        System.out.println("Bean of type " 
+                                           + beanObj.getClass().getName() 
+                                           + " doesn't implement home interface " 
+                                           + home.getName());
+                        throw new NamingException
+                            ("Bean of type " + beanObj.getClass().getName() 
+                             + " doesn't implement home interface " 
+                             + home.getName());
+                    }
+                } catch (ClassNotFoundException e) {
+                    System.out.println("Couldn't load home interface "
+                                       + homeClassName);
+                }
+                */
+                return beanObj;
+            }
+            
+            ObjectFactory factory = null;
+            RefAddr factoryRefAddr = ref.get(Constants.FACTORY);
+            if (factoryRefAddr != null) {
+                // Using the specified factory
+                String factoryClassName = 
+                    factoryRefAddr.getContent().toString();
+                // Loading factory
+                ClassLoader tcl = 
+                    Thread.currentThread().getContextClassLoader();
+                Class<?> factoryClass = null;
+                if (tcl != null) {
+                    try {
+                        factoryClass = tcl.loadClass(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                } else {
+                    try {
+                        factoryClass = Class.forName(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                }
+                if (factoryClass != null) {
+                    try {
+                        factory = (ObjectFactory) factoryClass.newInstance();
+                    } catch(Throwable t) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(t);
+                        throw ex;
+                    }
+                }
+            } else {
+                String javaxEjbFactoryClassName =
+                    System.getProperty("javax.ejb.Factory",
+                                       Constants.OPENEJB_EJB_FACTORY);
+                try {
+                    factory = (ObjectFactory)
+                        Class.forName(javaxEjbFactoryClassName).newInstance();
+                } catch(Throwable t) {
+                    if (t instanceof NamingException)
+                        throw (NamingException) t;
+                    NamingException ex = new NamingException
+                        ("Could not create resource factory instance");
+                    ex.initCause(t);
+                    throw ex;
+                }
+            }
+
+            if (factory != null) {
+                return factory.getObjectInstance
+                    (obj, name, nameCtx, environment);
+            } else {
+                throw new NamingException
+                    ("Cannot create resource instance");
+            }
+
+        }
+
+        return null;
+
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/MailSessionFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/MailSessionFactory.java
new file mode 100644
index 0000000..22a106a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/MailSessionFactory.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.naming.factory;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+/**
+ * <p>Factory class that creates a JNDI named JavaMail Session factory,
+ * which can be used for managing inbound and outbound electronic mail
+ * messages via JavaMail APIs.  All messaging environment properties
+ * described in the JavaMail Specification may be passed to the Session
+ * factory; however the following properties are the most commonly used:</p>
+ * <ul>
+ * <li>
+ * <li><strong>mail.smtp.host</strong> - Hostname for outbound transport
+ *     connections.  Defaults to <code>localhost</code> if not specified.</li>
+ * </ul>
+ *
+ * <p>This factory can be configured in a 
+ * <code>&lt;Context&gt;</code> element in your <code>conf/server.xml</code>
+ * configuration file.  An example of factory configuration is:</p>
+ * <pre>
+ * &lt;Resource name="mail/smtp" auth="CONTAINER"
+ *           type="javax.mail.Session"/&gt;
+ * &lt;ResourceParams name="mail/smtp"&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;factory&lt;/name&gt;
+ *     &lt;value&gt;org.apache.naming.factory.MailSessionFactory&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ *   &lt;parameter&gt;
+ *     &lt;name&gt;mail.smtp.host&lt;/name&gt;
+ *     &lt;value&gt;mail.mycompany.com&lt;/value&gt;
+ *   &lt;/parameter&gt;
+ * &lt;/ResourceParams&gt;
+ * </pre>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MailSessionFactory.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+
+public class MailSessionFactory implements ObjectFactory {
+
+
+    /**
+     * The Java type for which this factory knows how to create objects.
+     */
+    protected static final String factoryType = "javax.mail.Session";
+
+
+    /**
+     * Create and return an object instance based on the specified
+     * characteristics.
+     *
+     * @param refObj Reference information containing our parameters, or null
+     *  if there are no parameters
+     * @param name The name of this object, relative to context, or null
+     *  if there is no name
+     * @param context The context to which name is relative, or null if name
+     *  is relative to the default initial context
+     * @param env Environment variables, or null if there are none
+     *
+     * @exception Exception if an error occurs during object creation
+     */
+    @Override
+    public Object getObjectInstance(Object refObj, Name name, Context context,
+            Hashtable<?,?> env) throws Exception {
+
+        // Return null if we cannot create an object of the requested type
+        final Reference ref = (Reference) refObj;
+        if (!ref.getClassName().equals(factoryType))
+            return (null);
+
+        // Create a new Session inside a doPrivileged block, so that JavaMail
+        // can read its default properties without throwing Security
+        // exceptions.
+        //
+        // Bugzilla 31288, 33077: add support for authentication.
+        return AccessController.doPrivileged(new PrivilegedAction<Session>() {
+                @Override
+                public Session run() {
+
+                    // Create the JavaMail properties we will use
+                    Properties props = new Properties();
+                    props.put("mail.transport.protocol", "smtp");
+                    props.put("mail.smtp.host", "localhost");
+
+                    String password = null;
+
+                    Enumeration<RefAddr> attrs = ref.getAll();
+                    while (attrs.hasMoreElements()) {
+                        RefAddr attr = attrs.nextElement();
+                        if ("factory".equals(attr.getType())) {
+                            continue;
+                        }
+
+                        if ("password".equals(attr.getType())) {
+                            password = (String) attr.getContent();
+                            continue;
+                        }
+
+                        props.put(attr.getType(), attr.getContent());
+                    }
+
+                    Authenticator auth = null;
+                    if (password != null) {
+                        String user = props.getProperty("mail.smtp.user");
+                        if(user == null) {
+                            user = props.getProperty("mail.user");
+                        }
+                        
+                        if(user != null) {
+                            final PasswordAuthentication pa = new PasswordAuthentication(user, password);
+                            auth = new Authenticator() {
+                                    @Override
+                                    protected PasswordAuthentication getPasswordAuthentication() {
+                                        return pa;
+                                    }
+                                };
+                        }
+                    }
+
+                    // Create and return the new Session object
+                    Session session = Session.getInstance(props, auth);
+                    return (session);
+
+                }
+        } );
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/OpenEjbFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/OpenEjbFactory.java
new file mode 100644
index 0000000..16fa94f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/OpenEjbFactory.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+import java.util.Hashtable;
+import java.util.Properties;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.EjbRef;
+
+/**
+ * Object factory for EJBs.
+ * 
+ * @author Jacek Laskowski
+ * @author Remy Maucherat
+ * @version $Id: OpenEjbFactory.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+public class OpenEjbFactory implements ObjectFactory {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    protected static final String DEFAULT_OPENEJB_FACTORY = 
+        "org.openejb.client.LocalInitialContextFactory";
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Create a new EJB instance using OpenEJB.
+     * 
+     * @param obj The reference object describing the DataSource
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws Exception {
+
+        Object beanObj = null;
+
+        if (obj instanceof EjbRef) {
+
+            Reference ref = (Reference) obj;
+
+            String factory = DEFAULT_OPENEJB_FACTORY;
+            RefAddr factoryRefAddr = ref.get("openejb.factory");
+            if (factoryRefAddr != null) {
+                // Retrieving the OpenEJB factory
+                factory = factoryRefAddr.getContent().toString();
+            }
+
+            Properties env = new Properties();
+            env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
+
+            RefAddr linkRefAddr = ref.get("openejb.link");
+            if (linkRefAddr != null) {
+                String ejbLink = linkRefAddr.getContent().toString();
+                beanObj = (new InitialContext(env)).lookup(ejbLink);
+            }
+
+        }
+
+        return beanObj;
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceEnvFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceEnvFactory.java
new file mode 100644
index 0000000..e06f3f2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceEnvFactory.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.ResourceEnvRef;
+
+/**
+ * Object factory for Resources env.
+ * 
+ * @author Remy Maucherat
+ * @version $Id: ResourceEnvFactory.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+
+public class ResourceEnvFactory
+    implements ObjectFactory {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Create a new Resource env instance.
+     * 
+     * @param obj The reference object describing the DataSource
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws Exception {
+        
+        if (obj instanceof ResourceEnvRef) {
+            Reference ref = (Reference) obj;
+            ObjectFactory factory = null;
+            RefAddr factoryRefAddr = ref.get(Constants.FACTORY);
+            if (factoryRefAddr != null) {
+                // Using the specified factory
+                String factoryClassName = 
+                    factoryRefAddr.getContent().toString();
+                // Loading factory
+                ClassLoader tcl = 
+                    Thread.currentThread().getContextClassLoader();
+                Class<?> factoryClass = null;
+                if (tcl != null) {
+                    try {
+                        factoryClass = tcl.loadClass(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                } else {
+                    try {
+                        factoryClass = Class.forName(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                }
+                if (factoryClass != null) {
+                    try {
+                        factory = (ObjectFactory) factoryClass.newInstance();
+                    } catch(Throwable t) {
+                        if (t instanceof NamingException)
+                            throw (NamingException) t;
+                        NamingException ex = new NamingException
+                            ("Could not create resource factory instance");
+                        ex.initCause(t);
+                        throw ex;
+                    }
+                }
+            }
+            // Note: No defaults here
+            if (factory != null) {
+                return factory.getObjectInstance
+                    (obj, name, nameCtx, environment);
+            } else {
+                throw new NamingException
+                    ("Cannot create resource instance");
+            }
+        }
+
+        return null;
+
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceFactory.java
new file mode 100644
index 0000000..2152dee
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceFactory.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.ResourceRef;
+
+/**
+ * Object factory for Resources.
+ * 
+ * @author Remy Maucherat
+ * @version $Id: ResourceFactory.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+
+public class ResourceFactory
+    implements ObjectFactory {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Crete a new DataSource instance.
+     * 
+     * @param obj The reference object describing the DataSource
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws Exception {
+        
+        if (obj instanceof ResourceRef) {
+            Reference ref = (Reference) obj;
+            ObjectFactory factory = null;
+            RefAddr factoryRefAddr = ref.get(Constants.FACTORY);
+            if (factoryRefAddr != null) {
+                // Using the specified factory
+                String factoryClassName = 
+                    factoryRefAddr.getContent().toString();
+                // Loading factory
+                ClassLoader tcl = 
+                    Thread.currentThread().getContextClassLoader();
+                Class<?> factoryClass = null;
+                if (tcl != null) {
+                    try {
+                        factoryClass = tcl.loadClass(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                } else {
+                    try {
+                        factoryClass = Class.forName(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                }
+                if (factoryClass != null) {
+                    try {
+                        factory = (ObjectFactory) factoryClass.newInstance();
+                    } catch (Exception e) {
+                        if (e instanceof NamingException)
+                            throw (NamingException) e;
+                        NamingException ex = new NamingException
+                            ("Could not create resource factory instance");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                }
+            } else {
+                if (ref.getClassName().equals("javax.sql.DataSource")) {
+                    String javaxSqlDataSourceFactoryClassName =
+                        System.getProperty("javax.sql.DataSource.Factory",
+                                           Constants.DBCP_DATASOURCE_FACTORY);
+                    try {
+                        factory = (ObjectFactory) 
+                            Class.forName(javaxSqlDataSourceFactoryClassName)
+                            .newInstance();
+                    } catch (Exception e) {
+                        NamingException ex = new NamingException
+                            ("Could not create resource factory instance");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                } else if (ref.getClassName().equals("javax.mail.Session")) {
+                    String javaxMailSessionFactoryClassName =
+                        System.getProperty("javax.mail.Session.Factory",
+                                           "org.apache.naming.factory.MailSessionFactory");
+                    try {
+                        factory = (ObjectFactory) 
+                            Class.forName(javaxMailSessionFactoryClassName)
+                            .newInstance();
+                    } catch(Throwable t) {
+                        NamingException ex = new NamingException
+                            ("Could not create resource factory instance");
+                        ex.initCause(t);
+                        throw ex;
+                    }
+                }
+            }
+            if (factory != null) {
+                return factory.getObjectInstance
+                    (obj, name, nameCtx, environment);
+            } else {
+                throw new NamingException
+                    ("Cannot create resource instance");
+            }
+        }
+        
+        return null;
+
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceLinkFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceLinkFactory.java
new file mode 100644
index 0000000..b991c2a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/ResourceLinkFactory.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.ResourceLinkRef;
+
+
+/**
+ * <p>Object factory for resource links.</p>
+ * 
+ * @author Remy Maucherat
+ * @version $Id: ResourceLinkFactory.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+
+public class ResourceLinkFactory
+    implements ObjectFactory {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * Global naming context.
+     */
+    private static Context globalContext = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Set the global context (note: can only be used once).
+     * 
+     * @param newGlobalContext new global context value
+     */
+    public static void setGlobalContext(Context newGlobalContext) {
+        globalContext = newGlobalContext;
+    }
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Create a new DataSource instance.
+     * 
+     * @param obj The reference object describing the DataSource
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws NamingException {
+        
+        if (!(obj instanceof ResourceLinkRef))
+            return null;
+
+        // Can we process this request?
+        Reference ref = (Reference) obj;
+
+        // Read the global ref addr
+        String globalName = null;
+        RefAddr refAddr = ref.get(ResourceLinkRef.GLOBALNAME);
+        if (refAddr != null) {
+            globalName = refAddr.getContent().toString();
+            Object result = null;
+            result = globalContext.lookup(globalName);
+            // FIXME: Check type
+            return result;
+        }
+
+        return (null);
+
+        
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/SendMailFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/SendMailFactory.java
new file mode 100644
index 0000000..16aeb79
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/SendMailFactory.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.naming.factory;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimePartDataSource;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+/**
+ * Factory class that creates a JNDI named javamail MimePartDataSource
+ * object which can be used for sending email using SMTP.
+ * <p>
+ * Can be configured in the Context scope
+ * of your server.xml configuration file.
+ * <p>
+ * Example:
+ * <p>
+ * <pre>
+ * &lt;Resource name="mail/send" auth="CONTAINER"
+ *           type="javax.mail.internet.MimePartDataSource"/>
+ * &lt;ResourceParams name="mail/send">
+ *   &lt;parameter>&lt;name>factory&lt;/name>
+ *     &lt;value>org.apache.naming.factory.SendMailFactory&lt;/value>
+ *   &lt;/parameter>
+ *   &lt;parameter>&lt;name>mail.smtp.host&lt;/name>
+ *     &lt;value>your.smtp.host&lt;/value>
+ *   &lt;/parameter>
+ *   &lt;parameter>&lt;name>mail.smtp.user&lt;/name>
+ *     &lt;value>someuser&lt;/value>
+ *   &lt;/parameter>
+ *   &lt;parameter>&lt;name>mail.from&lt;/name>
+ *     &lt;value>someuser@some.host&lt;/value>
+ *   &lt;/parameter>
+ *   &lt;parameter>&lt;name>mail.smtp.sendpartial&lt;/name>
+ *     &lt;value>true&lt;/value>
+ *   &lt;/parameter>
+ *  &lt;parameter>&lt;name>mail.smtp.dsn.notify&lt;/name>
+ *     &lt;value>FAILURE&lt;/value>
+ *   &lt;/parameter>
+ *   &lt;parameter>&lt;name>mail.smtp.dsn.ret&lt;/name>
+ *     &lt;value>FULL&lt;/value>
+ *   &lt;/parameter>
+ * &lt;/ResourceParams>
+ * </pre>
+ *
+ * @author Glenn Nielsen Rich Catlett
+ */
+
+public class SendMailFactory implements ObjectFactory 
+{
+    // The class name for the javamail MimeMessageDataSource
+    protected final String DataSourceClassName = 
+        "javax.mail.internet.MimePartDataSource";
+
+    @Override
+    public Object getObjectInstance(Object refObj, Name name, Context ctx,
+            Hashtable<?,?> env) throws Exception {
+        final Reference ref = (Reference)refObj;
+
+        // Creation of the DataSource is wrapped inside a doPrivileged
+        // so that javamail can read its default properties without
+        // throwing Security Exceptions
+        if (ref.getClassName().equals(DataSourceClassName)) {
+            return AccessController.doPrivileged(
+                    new PrivilegedAction<MimePartDataSource>()
+            {
+                @Override
+                public MimePartDataSource run() {
+                    // set up the smtp session that will send the message
+                    Properties props = new Properties();
+                    // enumeration of all refaddr
+                    Enumeration<RefAddr> list = ref.getAll();
+                    // current refaddr to be set
+                    RefAddr refaddr;
+                    // set transport to smtp
+                    props.put("mail.transport.protocol", "smtp");
+
+                    while (list.hasMoreElements()) {
+                        refaddr = list.nextElement();
+
+                        // set property
+                        props.put(refaddr.getType(), refaddr.getContent());
+                    }
+                    MimeMessage message = new MimeMessage(
+                        Session.getInstance(props));
+                    try {
+                        RefAddr fromAddr = ref.get("mail.from");
+                        String from = null;
+                        if (fromAddr != null) {
+                            from = (String)ref.get("mail.from").getContent();
+                        }
+                        if (from != null) {
+                            message.setFrom(new InternetAddress(from));
+                        }
+                        message.setSubject("");
+                    } catch (Exception e) {/*Ignore*/}
+                    MimePartDataSource mds = new MimePartDataSource(message);
+                    return mds;
+                }
+            } );
+        }
+        else { // We can't create an instance of the DataSource
+            return null;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/TransactionFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/factory/TransactionFactory.java
new file mode 100644
index 0000000..84d923b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/TransactionFactory.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.factory;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.TransactionRef;
+
+/**
+ * Object factory for User transactions.
+ * 
+ * @author Remy Maucherat
+ * @version $Id: TransactionFactory.java,v 1.1 2011/06/28 21:14:11 rherrmann Exp $
+ */
+
+public class TransactionFactory
+    implements ObjectFactory {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Create a new User transaction instance.
+     * 
+     * @param obj The reference object describing the DataSource
+     */
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws Exception {
+        
+        if (obj instanceof TransactionRef) {
+            Reference ref = (Reference) obj;
+            ObjectFactory factory = null;
+            RefAddr factoryRefAddr = ref.get(Constants.FACTORY);
+            if (factoryRefAddr != null) {
+                // Using the specified factory
+                String factoryClassName = 
+                    factoryRefAddr.getContent().toString();
+                // Loading factory
+                ClassLoader tcl = 
+                    Thread.currentThread().getContextClassLoader();
+                Class<?> factoryClass = null;
+                if (tcl != null) {
+                    try {
+                        factoryClass = tcl.loadClass(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                } else {
+                    try {
+                        factoryClass = Class.forName(factoryClassName);
+                    } catch(ClassNotFoundException e) {
+                        NamingException ex = new NamingException
+                            ("Could not load resource factory class");
+                        ex.initCause(e);
+                        throw ex;
+                    }
+                }
+                if (factoryClass != null) {
+                    try {
+                        factory = (ObjectFactory) factoryClass.newInstance();
+                    } catch(Throwable t) {
+                        if (t instanceof NamingException)
+                            throw (NamingException) t;
+                        NamingException ex = new NamingException
+                            ("Could not create resource factory instance");
+                        ex.initCause(t);
+                        throw ex;
+                    }
+                }
+            }
+            if (factory != null) {
+                return factory.getObjectInstance
+                    (obj, name, nameCtx, environment);
+            } else {
+                throw new NamingException
+                    ("Cannot create resource instance");
+            }
+            
+        }
+        
+        return null;
+        
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/factory/package.html b/bundles/org.apache.tomcat/src/org/apache/naming/factory/package.html
new file mode 100644
index 0000000..5f65a4f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/factory/package.html
@@ -0,0 +1,23 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<body>
+
+<p>This package contains object factories used by the naming service.</p>
+
+<p></p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/BaseDirContext.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/BaseDirContext.java
new file mode 100644
index 0000000..238ac97
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/BaseDirContext.java
@@ -0,0 +1,1628 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.resources;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.apache.naming.NameParserImpl;
+import org.apache.naming.NamingContextBindingsEnumeration;
+import org.apache.naming.NamingEntry;
+import org.apache.naming.StringManager;
+
+/**
+ * Directory Context implementation helper class.
+ *
+ * @author Remy Maucherat
+ * @version $Id: BaseDirContext.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+
+public abstract class BaseDirContext implements DirContext {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( BaseDirContext.class );
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Builds a base directory context.
+     */
+    public BaseDirContext() {
+        this.env = new Hashtable<String,Object>();
+    }
+
+
+    /**
+     * Builds a base directory context using the given environment.
+     */
+    public BaseDirContext(Hashtable<String,Object> env) {
+        this.env = env;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The document base path.
+     */
+    protected String docBase = null;
+
+
+    /**
+     * Environment.
+     */
+    protected Hashtable<String,Object> env;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Name parser for this context.
+     */
+    protected final NameParser nameParser = new NameParserImpl();
+
+
+    /**
+     * Cached.
+     */
+    protected boolean cached = true;
+
+
+    /**
+     * Cache TTL.
+     */
+    protected int cacheTTL = 5000; // 5s
+
+
+    /**
+     * Max size of cache for resources.
+     */
+    protected int cacheMaxSize = 10240; // 10 MB
+
+
+    /**
+     * Max size of resources that will be content cached.
+     */
+    protected int cacheObjectMaxSize = 512; // 512 K
+
+
+    /**
+     * Aliases allow content to be included from other locations.
+     */
+    protected Map<String,BaseDirContext> aliases =
+        new HashMap<String,BaseDirContext>();
+
+
+    /**
+     * Alternate / backup DirContexts for static resources. These will be
+     * searched in the order they are added if the requested resource cannot be
+     * found in the primary DirContext. 
+     */
+    protected List<DirContext> altDirContexts = new ArrayList<DirContext>();
+    
+    
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Add a resources JAR. The contents of /META-INF/resources/ will be used if
+     * a requested resource can not be found in the main context.
+     */
+    public void addResourcesJar(URL url) {
+        try {
+            JarURLConnection conn = (JarURLConnection) url.openConnection();
+            JarFile jarFile = conn.getJarFile();   
+            ZipEntry entry = jarFile.getEntry("/");
+            WARDirContext warDirContext = new WARDirContext(jarFile,
+                    new WARDirContext.Entry("/", entry));
+            warDirContext.loadEntries();
+            altDirContexts.add(warDirContext);
+        } catch (IOException ioe) {
+            log.warn(sm.getString("resources.addResourcesJarFail", url), ioe);
+        }
+    }
+    
+
+    /**
+     * Add an alternative DirContext (must contain META-INF/resources) directly.
+     */
+    public void addAltDirContext(DirContext altDirContext) {
+        altDirContexts.add(altDirContext);
+    }
+
+
+    /**
+     * Add an alias.
+     */
+    public void addAlias(String path, BaseDirContext dirContext) {
+        if (!path.startsWith("/")) {
+            throw new IllegalArgumentException(
+                    sm.getString("resources.invalidAliasPath", path));
+        }
+        aliases.put(path, dirContext);
+    }
+
+    
+    /**
+     * Remove an alias.
+     */
+    public void removeAlias(String path) {
+        if (!path.startsWith("/")) {
+            throw new IllegalArgumentException(
+                    sm.getString("resources.invalidAliasPath", path));
+        }
+        aliases.remove(path);
+    }
+    
+    
+    /**
+     * Get the current alias configuration in String form. If no aliases are
+     * configured, an empty string will be returned.
+     */
+    public String getAliases() {
+        StringBuilder result = new StringBuilder();
+        Iterator<Entry<String,BaseDirContext>> iter =
+            aliases.entrySet().iterator();
+        boolean first = true;
+        while (iter.hasNext()) {
+            if (first) {
+                first = false;
+            } else {
+                result.append(',');
+            }
+            Entry<String,BaseDirContext> entry = iter.next();
+            result.append(entry.getKey());
+            result.append('=');
+            result.append(entry.getValue().getDocBase());
+        }
+        return result.toString();
+    }
+
+    
+    /**
+     * Set the current alias configuration from a String. The String should be
+     * of the form "/aliasPath1=docBase1,/aliasPath2=docBase2" where aliasPathN
+     * must include a leading '/' and docBaseN must be an absolute path to
+     * either a .war file or a directory. Any call to this method will replace
+     * the current set of aliases.
+     */
+    public void setAliases(String theAliases) {
+        // Overwrite whatever is currently set
+        aliases.clear();
+        
+        if (theAliases == null || theAliases.length() == 0)
+            return;
+        
+        String[] kvps = theAliases.split(",");
+        for (String kvp : kvps) {
+            String[] kv = kvp.split("=");
+            if (kv.length != 2 || kv[0].length() == 0 || kv[1].length() == 0)
+                throw new IllegalArgumentException(
+                        sm.getString("resources.invalidAliasMapping", kvp));
+
+            if (kv[0].equals("/")) {
+                throw new IllegalArgumentException(
+                        sm.getString("resources.invalidAliasNotAllowed", kv[0]));
+            }
+            File aliasLoc = new File(kv[1]);
+            if (!aliasLoc.exists()) {
+                throw new IllegalArgumentException(
+                        sm.getString("resources.invalidAliasNotExist", kv[1]));
+            }
+            BaseDirContext context;
+            if (kv[1].endsWith(".war") && !(aliasLoc.isDirectory())) {
+                context = new WARDirContext();
+            } else if (aliasLoc.isDirectory()) {
+                context = new FileDirContext();
+            } else {
+                throw new IllegalArgumentException(
+                        sm.getString("resources.invalidAliasFile", kv[1]));
+            }
+            context.setDocBase(kv[1]);
+            addAlias(kv[0], context);
+        }
+    }
+
+    
+    /**
+     * Return the document root for this component.
+     */
+    public String getDocBase() {
+        return (this.docBase);
+    }
+
+
+    /**
+     * Set the document root for this component.
+     *
+     * @param docBase The new document root
+     *
+     * @exception IllegalArgumentException if the specified value is not
+     *  supported by this implementation
+     * @exception IllegalArgumentException if this would create a
+     *  malformed URL
+     */
+    public void setDocBase(String docBase) {
+
+        // Validate the format of the proposed document root
+        if (docBase == null)
+            throw new IllegalArgumentException
+                (sm.getString("resources.null"));
+
+        // Change the document root property
+        this.docBase = docBase;
+
+    }
+
+
+    /**
+     * Set cached.
+     */
+    public void setCached(boolean cached) {
+        this.cached = cached;
+    }
+
+
+    /**
+     * Is cached ?
+     */
+    public boolean isCached() {
+        return cached;
+    }
+
+
+    /**
+     * Set cache TTL.
+     */
+    public void setCacheTTL(int cacheTTL) {
+        this.cacheTTL = cacheTTL;
+    }
+
+
+    /**
+     * Get cache TTL.
+     */
+    public int getCacheTTL() {
+        return cacheTTL;
+    }
+
+
+    /**
+     * Return the maximum size of the cache in KB.
+     */
+    public int getCacheMaxSize() {
+        return cacheMaxSize;
+    }
+
+
+    /**
+     * Set the maximum size of the cache in KB.
+     */
+    public void setCacheMaxSize(int cacheMaxSize) {
+        this.cacheMaxSize = cacheMaxSize;
+    }
+
+
+    /**
+     * Return the maximum size of objects to be cached in KB.
+     */
+    public int getCacheObjectMaxSize() {
+        return cacheObjectMaxSize;
+    }
+
+
+    /**
+     * Set the maximum size of objects to be placed the cache in KB.
+     */
+    public void setCacheObjectMaxSize(int cacheObjectMaxSize) {
+        this.cacheObjectMaxSize = cacheObjectMaxSize;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Allocate resources for this directory context.
+     */
+    public void allocate() {
+        // No action taken by the default implementation
+    }
+
+
+    /**
+     * Release any resources allocated for this directory context.
+     */
+    public void release() {
+        for(BaseDirContext bcontext: this.aliases.values()) {
+            bcontext.release();
+        }
+        this.aliases.clear();
+        for(DirContext dcontext: this.altDirContexts) {
+            if(dcontext instanceof BaseDirContext) {
+                ((BaseDirContext)dcontext).release();
+            }
+        }
+        this.altDirContexts.clear();        
+    }
+
+    
+    /**
+     * Return the real path for a given virtual path, if possible; otherwise
+     * return <code>null</code>.
+     *
+     * @param name The path to the desired resource
+     */
+    public String getRealPath(String name) {
+        if (!aliases.isEmpty()) {
+            AliasResult result = findAlias(name);
+            if (result.dirContext != null) {
+                return result.dirContext.doGetRealPath(result.aliasName);
+            }
+        }
+        
+        // Next do a standard getRealPath()
+        String path = doGetRealPath(name);
+
+        if (path != null)
+            return path;
+        
+        // Check the alternate locations
+        for (DirContext altDirContext : altDirContexts) {
+            if (altDirContext instanceof BaseDirContext){
+                path = ((BaseDirContext) altDirContext).getRealPath(
+                        "/META-INF/resources" + name);
+                if (path != null)
+                    return path;
+            }
+        }
+        
+        // Really not found
+        return null; 
+    }
+
+    // -------------------------------------------------------- Context Methods
+
+
+    /**
+     * Retrieves the named object. If name is empty, returns a new instance 
+     * of this context (which represents the same naming context as this 
+     * context, but its environment may be modified independently and it may 
+     * be accessed concurrently).
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public final Object lookup(Name name)
+        throws NamingException {
+        return lookup(name.toString());
+    }
+
+
+    /**
+     * Retrieves the named object.
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public final Object lookup(String name) throws NamingException {
+        // First check for aliases
+        if (!aliases.isEmpty()) {
+            AliasResult result = findAlias(name);
+            if (result.dirContext != null) {
+                return result.dirContext.lookup(result.aliasName);
+            }
+        }
+        
+        // Next do a standard lookup
+        Object obj = doLookup(name);
+
+        if (obj != null)
+            return obj;
+        
+        // Check the alternate locations
+        for (DirContext altDirContext : altDirContexts) {
+            try {
+                obj = altDirContext.lookup("/META-INF/resources" + name);
+                if (obj != null)
+                    return obj;
+            } catch ( NamingException ex) {
+                // ignore
+            }
+        }
+        
+        // Really not found
+        throw new NameNotFoundException(
+                sm.getString("resources.notFound", name));
+    }
+
+    /**
+     * Binds a name to an object. All intermediate contexts and the target 
+     * context (that named by all but terminal atomic component of the name) 
+     * must already exist.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(Name name, Object obj)
+        throws NamingException {
+        bind(name.toString(), obj);
+    }
+
+
+    /**
+     * Binds a name to an object.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(String name, Object obj)
+        throws NamingException {
+        bind(name, obj, null);
+    }
+
+
+    /**
+     * Binds a name to an object, overwriting any existing binding. All 
+     * intermediate contexts and the target context (that named by all but 
+     * terminal atomic component of the name) must already exist.
+     * <p>
+     * If the object is a DirContext, any existing attributes associated with 
+     * the name are replaced with those of the object. Otherwise, any 
+     * existing attributes associated with the name remain unchanged.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(Name name, Object obj)
+        throws NamingException {
+        rebind(name.toString(), obj);
+    }
+
+
+    /**
+     * Binds a name to an object, overwriting any existing binding.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(String name, Object obj)
+        throws NamingException {
+        rebind(name, obj, null);
+    }
+
+
+    /**
+     * Unbinds the named object. Removes the terminal atomic name in name 
+     * from the target context--that named by all but the terminal atomic 
+     * part of name.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic 
+     * name is not bound in the target context, but throws 
+     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * 
+     * @param name the name to bind; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void unbind(Name name)
+        throws NamingException {
+        unbind(name.toString());
+    }
+
+
+    /**
+     * Unbinds the named object.
+     * 
+     * @param name the name to bind; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract void unbind(String name)
+        throws NamingException;
+
+
+    /**
+     * Binds a new name to the object bound to an old name, and unbinds the 
+     * old name. Both names are relative to this context. Any attributes 
+     * associated with the old name become associated with the new name. 
+     * Intermediate contexts of the old name are not changed.
+     * 
+     * @param oldName the name of the existing binding; may not be empty
+     * @param newName the name of the new binding; may not be empty
+     * @exception javax.naming.NameAlreadyBoundException if newName is already
+     * bound
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rename(Name oldName, Name newName)
+        throws NamingException {
+        rename(oldName.toString(), newName.toString());
+    }
+
+
+    /**
+     * Binds a new name to the object bound to an old name, and unbinds the 
+     * old name.
+     * 
+     * @param oldName the name of the existing binding; may not be empty
+     * @param newName the name of the new binding; may not be empty
+     * @exception javax.naming.NameAlreadyBoundException if newName is already
+     * bound
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract void rename(String oldName, String newName)
+        throws NamingException;
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the class 
+     * names of objects bound to them. The contents of any subcontexts are 
+     * not included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on 
+     * an enumeration previously returned is undefined.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the names and class names of the bindings in 
+     * this context. Each element of the enumeration is of type NameClassPair.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<NameClassPair> list(Name name)
+        throws NamingException {
+        return list(name.toString());
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the class 
+     * names of objects bound to them.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the names and class names of the bindings in 
+     * this context. Each element of the enumeration is of type NameClassPair.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract NamingEnumeration<NameClassPair> list(String name)
+        throws NamingException;
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the 
+     * objects bound to them. The contents of any subcontexts are not 
+     * included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on 
+     * an enumeration previously returned is undefined.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the bindings in this context. 
+     * Each element of the enumeration is of type Binding.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public final NamingEnumeration<Binding> listBindings(Name name)
+        throws NamingException {
+        return listBindings(name.toString());
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the 
+     * objects bound to them.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the bindings in this context. 
+     * Each element of the enumeration is of type Binding.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public final NamingEnumeration<Binding> listBindings(String name)
+        throws NamingException {
+        if (!aliases.isEmpty()) {
+            AliasResult result = findAlias(name);
+            if (result.dirContext != null) {
+                return result.dirContext.listBindings(result.aliasName);
+            }
+        }
+        
+        // Next do a standard lookup
+        List<NamingEntry> bindings = doListBindings(name);
+
+        // Check the alternate locations
+        List<NamingEntry> altBindings = null;
+
+        for (DirContext altDirContext : altDirContexts) {
+            if (altDirContext instanceof BaseDirContext) {
+                altBindings = ((BaseDirContext) altDirContext).doListBindings(
+                        "/META-INF/resources" + name);
+            }
+            if (altBindings != null) {
+                if (bindings == null) {
+                    bindings = altBindings;
+                } else {
+                    bindings.addAll(altBindings);
+                }
+            }
+        }
+
+        if (bindings != null) {
+            return new NamingContextBindingsEnumeration(bindings.iterator(),
+                    this);
+        }
+
+        // Really not found
+        throw new NameNotFoundException(
+                sm.getString("resources.notFound", name));
+    }
+
+
+    /**
+     * Destroys the named context and removes it from the namespace. Any 
+     * attributes associated with the name are also removed. Intermediate 
+     * contexts are not destroyed.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic 
+     * name is not bound in the target context, but throws 
+     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * 
+     * In a federated naming system, a context from one naming system may be 
+     * bound to a name in another. One can subsequently look up and perform 
+     * operations on the foreign context using a composite name. However, an 
+     * attempt destroy the context using this composite name will fail with 
+     * NotContextException, because the foreign context is not a "subcontext" 
+     * of the context in which it is bound. Instead, use unbind() to remove 
+     * the binding of the foreign context. Destroying the foreign context 
+     * requires that the destroySubcontext() be performed on a context from 
+     * the foreign context's "native" naming system.
+     * 
+     * @param name the name of the context to be destroyed; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception javax.naming.NotContextException if the name is bound but does
+     * not name a context, or does not name a context of the appropriate type
+     */
+    @Override
+    public void destroySubcontext(Name name)
+        throws NamingException {
+        destroySubcontext(name.toString());
+    }
+
+
+    /**
+     * Destroys the named context and removes it from the namespace.
+     * 
+     * @param name the name of the context to be destroyed; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception javax.naming.NotContextException if the name is bound but does
+     * not name a context, or does not name a context of the appropriate type
+     */
+    @Override
+    public abstract void destroySubcontext(String name)
+        throws NamingException;
+
+
+    /**
+     * Creates and binds a new context. Creates a new context with the given 
+     * name and binds it in the target context (that named by all but 
+     * terminal atomic component of the name). All intermediate contexts and 
+     * the target context must already exist.
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if creation
+     * of the sub-context requires specification of mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Context createSubcontext(Name name)
+        throws NamingException {
+        return createSubcontext(name.toString());
+    }
+
+
+    /**
+     * Creates and binds a new context.
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if creation
+     * of the sub-context requires specification of mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Context createSubcontext(String name)
+        throws NamingException {
+        return createSubcontext(name, null);
+    }
+
+
+    /**
+     * Retrieves the named object, following links except for the terminal 
+     * atomic component of the name. If the object bound to name is not a 
+     * link, returns the object itself.
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name, not following the terminal link 
+     * (if any).
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object lookupLink(Name name)
+        throws NamingException {
+        return lookupLink(name.toString());
+    }
+
+
+    /**
+     * Retrieves the named object, following links except for the terminal 
+     * atomic component of the name.
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name, not following the terminal link 
+     * (if any).
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract Object lookupLink(String name)
+        throws NamingException;
+
+
+    /**
+     * Retrieves the parser associated with the named context. In a 
+     * federation of namespaces, different naming systems will parse names 
+     * differently. This method allows an application to get a parser for 
+     * parsing names into their atomic components using the naming convention 
+     * of a particular naming system. Within any single naming system, 
+     * NameParser objects returned by this method must be equal (using the 
+     * equals() test).
+     * 
+     * @param name the name of the context from which to get the parser
+     * @return a name parser that can parse compound names into their atomic 
+     * components
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NameParser getNameParser(Name name)
+        throws NamingException {
+        return new NameParserImpl();
+    }
+
+
+    /**
+     * Retrieves the parser associated with the named context.
+     * 
+     * @param name the name of the context from which to get the parser
+     * @return a name parser that can parse compound names into their atomic 
+     * components
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NameParser getNameParser(String name)
+        throws NamingException {
+        return new NameParserImpl();
+    }
+
+
+    /**
+     * Composes the name of this context with a name relative to this context.
+     * <p>
+     * Given a name (name) relative to this context, and the name (prefix) 
+     * of this context relative to one of its ancestors, this method returns 
+     * the composition of the two names using the syntax appropriate for the 
+     * naming system(s) involved. That is, if name names an object relative 
+     * to this context, the result is the name of the same object, but 
+     * relative to the ancestor context. None of the names may be null.
+     * 
+     * @param name a name relative to this context
+     * @param prefix the name of this context relative to one of its ancestors
+     * @return the composition of prefix and name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Name composeName(Name name, Name prefix)
+        throws NamingException {
+        Name clone = (Name) prefix.clone();
+        return clone.addAll(name);
+    }
+
+
+    /**
+     * Composes the name of this context with a name relative to this context.
+     * 
+     * @param name a name relative to this context
+     * @param prefix the name of this context relative to one of its ancestors
+     * @return the composition of prefix and name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public String composeName(String name, String prefix)
+        throws NamingException {
+        return prefix + "/" + name;
+    }
+
+
+    /**
+     * Adds a new environment property to the environment of this context. If 
+     * the property already exists, its value is overwritten.
+     * 
+     * @param propName the name of the environment property to add; may not 
+     * be null
+     * @param propVal the value of the property to add; may not be null
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object addToEnvironment(String propName, Object propVal)
+        throws NamingException {
+        return env.put(propName, propVal);
+    }
+
+
+    /**
+     * Removes an environment property from the environment of this context. 
+     * 
+     * @param propName the name of the environment property to remove; 
+     * may not be null
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object removeFromEnvironment(String propName)
+        throws NamingException {
+        return env.remove(propName);
+    }
+
+
+    /**
+     * Retrieves the environment in effect for this context. See class 
+     * description for more details on environment properties. 
+     * The caller should not make any changes to the object returned: their 
+     * effect on the context is undefined. The environment of this context 
+     * may be changed using addToEnvironment() and removeFromEnvironment().
+     * 
+     * @return the environment of this context; never null
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Hashtable<String,Object> getEnvironment()
+        throws NamingException {
+        return env;
+    }
+
+
+    /**
+     * Closes this context. This method releases this context's resources 
+     * immediately, instead of waiting for them to be released automatically 
+     * by the garbage collector.
+     * This method is idempotent: invoking it on a context that has already 
+     * been closed has no effect. Invoking any other method on a closed 
+     * context is not allowed, and results in undefined behaviour.
+     * 
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void close()
+        throws NamingException {
+        env.clear();
+    }
+
+
+    /**
+     * Retrieves the full name of this context within its own namespace.
+     * <p>
+     * Many naming services have a notion of a "full name" for objects in 
+     * their respective namespaces. For example, an LDAP entry has a 
+     * distinguished name, and a DNS record has a fully qualified name. This 
+     * method allows the client application to retrieve this name. The string 
+     * returned by this method is not a JNDI composite name and should not be 
+     * passed directly to context methods. In naming systems for which the 
+     * notion of full name does not make sense, 
+     * OperationNotSupportedException is thrown.
+     * 
+     * @return this context's name in its own namespace; never null
+     * @exception javax.naming.OperationNotSupportedException if the naming
+     * system does not have the notion of a full name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract String getNameInNamespace()
+        throws NamingException;
+
+
+    // ----------------------------------------------------- DirContext Methods
+
+
+    /**
+     * Retrieves all of the attributes associated with a named object. 
+     * 
+     * @return the set of attributes associated with name. 
+     * Returns an empty attribute set if name has no attributes; never null.
+     * @param name the name of the object from which to retrieve attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Attributes getAttributes(Name name)
+        throws NamingException {
+        return getAttributes(name.toString());
+    }
+
+
+    /**
+     * Retrieves all of the attributes associated with a named object.
+     * 
+     * @return the set of attributes associated with name
+     * @param name the name of the object from which to retrieve attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Attributes getAttributes(String name)
+        throws NamingException {
+        return getAttributes(name, null);
+    }
+
+
+    /**
+     * Retrieves selected attributes associated with a named object. 
+     * See the class description regarding attribute models, attribute type 
+     * names, and operational attributes.
+     * 
+     * @return the requested attributes; never null
+     * @param name the name of the object from which to retrieve attributes
+     * @param attrIds the identifiers of the attributes to retrieve. null 
+     * indicates that all attributes should be retrieved; an empty array 
+     * indicates that none should be retrieved
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Attributes getAttributes(Name name, String[] attrIds)
+        throws NamingException {
+        return getAttributes(name.toString(), attrIds);
+    }
+    
+    
+    /**
+     * Retrieves selected attributes associated with a named object.
+     * 
+     * @return the requested attributes; never null
+     * @param name the name of the object from which to retrieve attributes
+     * @param attrIds the identifiers of the attributes to retrieve. null 
+     * indicates that all attributes should be retrieved; an empty array 
+     * indicates that none should be retrieved
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public final Attributes getAttributes(String name, String[] attrIds)
+        throws NamingException {
+        
+        // First check for aliases
+        if (!aliases.isEmpty()) {
+            AliasResult result = findAlias(name);
+            if (result.dirContext != null) {
+                return result.dirContext.getAttributes(
+                        result.aliasName, attrIds);
+            }
+        }
+        
+        // Next do a standard lookup
+        Attributes attrs = doGetAttributes(name, attrIds);
+
+        if (attrs != null)
+            return attrs;
+        
+        // Check the alternate locations
+        for (DirContext altDirContext : altDirContexts) {
+            if (altDirContext instanceof BaseDirContext)
+                attrs = ((BaseDirContext) altDirContext).doGetAttributes(
+                        "/META-INF/resources" + name, attrIds);
+            else {
+                try {
+                    attrs = altDirContext.getAttributes(name, attrIds);
+                } catch (NamingException ne) {
+                    // Ignore
+                }
+            }
+            if (attrs != null)
+                return attrs;
+        }
+        
+        // Really not found
+        throw new NameNotFoundException(
+                sm.getString("resources.notFound", name));
+    }
+
+    /**
+     * Modifies the attributes associated with a named object. The order of 
+     * the modifications is not specified. Where possible, the modifications 
+     * are performed atomically.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mod_op the modification operation, one of: ADD_ATTRIBUTE, 
+     * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
+     * @param attrs the attributes to be used for the modification; may not 
+     * be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(Name name, int mod_op, Attributes attrs)
+        throws NamingException {
+        modifyAttributes(name.toString(), mod_op, attrs);
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mod_op the modification operation, one of: ADD_ATTRIBUTE, 
+     * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
+     * @param attrs the attributes to be used for the modification; may not 
+     * be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract void modifyAttributes
+        (String name, int mod_op, Attributes attrs)
+        throws NamingException;
+
+
+    /**
+     * Modifies the attributes associated with a named object using an an 
+     * ordered list of modifications. The modifications are performed in the 
+     * order specified. Each modification specifies a modification operation 
+     * code and an attribute on which to operate. Where possible, the 
+     * modifications are performed atomically.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mods an ordered sequence of modifications to be performed; may 
+     * not be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(Name name, ModificationItem[] mods)
+        throws NamingException {
+        modifyAttributes(name.toString(), mods);
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object using an an 
+     * ordered list of modifications.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mods an ordered sequence of modifications to be performed; may 
+     * not be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract void modifyAttributes(String name, ModificationItem[] mods)
+        throws NamingException;
+
+
+    /**
+     * Binds a name to an object, along with associated attributes. If attrs 
+     * is null, the resulting binding will have the attributes associated 
+     * with obj if obj is a DirContext, and no attributes otherwise. If attrs 
+     * is non-null, the resulting binding will have attrs as its attributes; 
+     * any attributes associated with obj are ignored.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(Name name, Object obj, Attributes attrs)
+        throws NamingException {
+        bind(name.toString(), obj, attrs);
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract void bind(String name, Object obj, Attributes attrs)
+        throws NamingException;
+
+
+    /**
+     * Binds a name to an object, along with associated attributes, 
+     * overwriting any existing binding. If attrs is null and obj is a 
+     * DirContext, the attributes from obj are used. If attrs is null and obj 
+     * is not a DirContext, any existing attributes associated with the object
+     * already bound in the directory remain unchanged. If attrs is non-null, 
+     * any existing attributes associated with the object already bound in 
+     * the directory are removed and attrs is associated with the named 
+     * object. If obj is a DirContext and attrs is non-null, the attributes 
+     * of obj are ignored.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(Name name, Object obj, Attributes attrs)
+        throws NamingException {
+        rebind(name.toString(), obj, attrs);
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes, 
+     * overwriting any existing binding.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract void rebind(String name, Object obj, Attributes attrs)
+        throws NamingException;
+
+
+    /**
+     * Creates and binds a new context, along with associated attributes. 
+     * This method creates a new subcontext with the given name, binds it in 
+     * the target context (that named by all but terminal atomic component of 
+     * the name), and associates the supplied attributes with the newly 
+     * created object. All intermediate and target contexts must already 
+     * exist. If attrs is null, this method is equivalent to 
+     * Context.createSubcontext().
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @param attrs the attributes to associate with the newly created context
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if attrs
+     * does not contain all the mandatory attributes required for creation
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext createSubcontext(Name name, Attributes attrs)
+        throws NamingException {
+        return createSubcontext(name.toString(), attrs);
+    }
+
+
+    /**
+     * Creates and binds a new context, along with associated attributes.
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @param attrs the attributes to associate with the newly created context
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if attrs
+     * does not contain all the mandatory attributes required for creation
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract DirContext createSubcontext(String name, Attributes attrs)
+        throws NamingException;
+
+
+    /**
+     * Retrieves the schema associated with the named object. The schema 
+     * describes rules regarding the structure of the namespace and the 
+     * attributes stored within it. The schema specifies what types of 
+     * objects can be added to the directory and where they can be added; 
+     * what mandatory and optional attributes an object can have. The range 
+     * of support for schemas is directory-specific.
+     * 
+     * @param name the name of the object whose schema is to be retrieved
+     * @return the schema associated with the context; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchema(Name name)
+        throws NamingException {
+        return getSchema(name.toString());
+    }
+
+
+    /**
+     * Retrieves the schema associated with the named object.
+     * 
+     * @param name the name of the object whose schema is to be retrieved
+     * @return the schema associated with the context; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract DirContext getSchema(String name)
+        throws NamingException;
+
+
+    /**
+     * Retrieves a context containing the schema objects of the named 
+     * object's class definitions.
+     * 
+     * @param name the name of the object whose object class definition is to 
+     * be retrieved
+     * @return the DirContext containing the named object's class 
+     * definitions; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchemaClassDefinition(Name name)
+        throws NamingException {
+        return getSchemaClassDefinition(name.toString());
+    }
+
+
+    /**
+     * Retrieves a context containing the schema objects of the named 
+     * object's class definitions.
+     * 
+     * @param name the name of the object whose object class definition is to 
+     * be retrieved
+     * @return the DirContext containing the named object's class 
+     * definitions; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract DirContext getSchemaClassDefinition(String name)
+        throws NamingException;
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes, and retrieves selected attributes. The search is 
+     * performed using the default SearchControls settings.
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @param attributesToReturn the attributes to return. null indicates 
+     * that all attributes are to be returned; an empty array indicates that 
+     * none are to be returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(Name name,
+            Attributes matchingAttributes, String[] attributesToReturn)
+        throws NamingException {
+        return search(name.toString(), matchingAttributes, attributesToReturn);
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes, and retrieves selected attributes.
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @param attributesToReturn the attributes to return. null indicates 
+     * that all attributes are to be returned; an empty array indicates that 
+     * none are to be returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract NamingEnumeration<SearchResult> search
+        (String name, Attributes matchingAttributes,
+         String[] attributesToReturn)
+        throws NamingException;
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes. This method returns all the attributes of such objects. 
+     * It is equivalent to supplying null as the atributesToReturn parameter 
+     * to the method search(Name, Attributes, String[]).
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(Name name,
+            Attributes matchingAttributes) throws NamingException {
+        return search(name.toString(), matchingAttributes);
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes.
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract NamingEnumeration<SearchResult> search
+        (String name, Attributes matchingAttributes)
+        throws NamingException;
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filter the filter expression to use for the search; may not be 
+     * null
+     * @param cons the search controls that control the search. If null, 
+     * the default search controls are used (equivalent to 
+     * (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy 
+     * the filter; never null
+     * @exception javax.naming.directory.InvalidSearchFilterException if the
+     * search filter specified is not supported or understood by the underlying
+     * directory
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search
+        (Name name, String filter, SearchControls cons)
+        throws NamingException {
+        return search(name.toString(), filter, cons);
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filter the filter expression to use for the search; may not be 
+     * null
+     * @param cons the search controls that control the search. If null, 
+     * the default search controls are used (equivalent to 
+     * (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy 
+     * the filter; never null
+     * @exception javax.naming.directory.InvalidSearchFilterException if the
+     * search filter specified is not supported or understood by the underlying
+     * directory
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract NamingEnumeration<SearchResult> search(String name,
+            String filter, SearchControls cons)
+        throws NamingException;
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filterExpr the filter expression to use for the search. 
+     * The expression may contain variables of the form "{i}" where i is a 
+     * nonnegative integer. May not be null.
+     * @param filterArgs the array of arguments to substitute for the 
+     * variables in filterExpr. The value of filterArgs[i] will replace each 
+     * occurrence of "{i}". If null, equivalent to an empty array.
+     * @param cons the search controls that control the search. If null, the 
+     * default search controls are used (equivalent to (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisy the 
+     * filter; never null
+     * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i} 
+     * expressions where i is outside the bounds of the array filterArgs
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception javax.naming.directory.InvalidSearchFilterException if
+     * filterExpr with filterArgs represents an invalid search filter
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(Name name, String filterExpr, 
+                                    Object[] filterArgs, SearchControls cons)
+        throws NamingException {
+        return search(name.toString(), filterExpr, filterArgs, cons);
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filterExpr the filter expression to use for the search. 
+     * The expression may contain variables of the form "{i}" where i is a 
+     * nonnegative integer. May not be null.
+     * @param filterArgs the array of arguments to substitute for the 
+     * variables in filterExpr. The value of filterArgs[i] will replace each 
+     * occurrence of "{i}". If null, equivalent to an empty array.
+     * @param cons the search controls that control the search. If null, the 
+     * default search controls are used (equivalent to (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy the 
+     * filter; never null
+     * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i} 
+     * expressions where i is outside the bounds of the array filterArgs
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception javax.naming.directory.InvalidSearchFilterException if
+     * filterExpr with filterArgs represents an invalid search filter
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public abstract NamingEnumeration<SearchResult> search
+        (String name, String filterExpr, 
+         Object[] filterArgs, SearchControls cons)
+        throws NamingException;
+
+
+    // ------------------------------------------------------ Protected Methods
+
+    protected abstract Attributes doGetAttributes(String name, String[] attrIds)
+        throws NamingException;
+
+    protected abstract Object doLookup(String name);
+
+    protected abstract List<NamingEntry> doListBindings(String name)
+        throws NamingException;
+
+    protected abstract String doGetRealPath(String name);
+
+    // -------------------------------------------------------- Private Methods
+    private AliasResult findAlias(String name) {
+        AliasResult result = new AliasResult();
+        
+        String searchName = name;
+        
+        result.dirContext = aliases.get(searchName);
+        while (result.dirContext == null) {
+            int slash = searchName.lastIndexOf('/');
+            if (slash < 0)
+                break;
+            searchName = searchName.substring(0, slash);
+            result.dirContext = aliases.get(searchName);
+        }
+        
+        if (result.dirContext != null)
+            result.aliasName = name.substring(searchName.length());
+        
+        return result;
+    }
+
+    private static class AliasResult {
+        BaseDirContext dirContext;
+        String aliasName;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/CacheEntry.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/CacheEntry.java
new file mode 100644
index 0000000..c29e646
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/CacheEntry.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import javax.naming.directory.DirContext;
+
+/**
+ * Implements a cache entry.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class CacheEntry {
+    
+    
+    // ------------------------------------------------- Instance Variables
+
+
+    public long timestamp = -1;
+    public String name = null;
+    public ResourceAttributes attributes = null;
+    public Resource resource = null;
+    public DirContext context = null;
+    public boolean exists = true;
+    public long accessCount = 0;
+    public int size = 1;
+
+
+    // ----------------------------------------------------- Public Methods
+
+
+    public void recycle() {
+        timestamp = -1;
+        name = null;
+        attributes = null;
+        resource = null;
+        context = null;
+        exists = true;
+        accessCount = 0;
+        size = 1;
+    }
+
+
+    @Override
+    public String toString() {
+        return ("Cache entry: " + name + "\n"
+                + "Exists: " + exists + "\n"
+                + "Attributes: " + attributes + "\n"
+                + "Resource: " + resource + "\n"
+                + "Context: " + context);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/Constants.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/Constants.java
new file mode 100644
index 0000000..757f19d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/Constants.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.resources;
+
+
+/**
+ * Static constants for this package.
+ */
+
+public final class Constants {
+
+    public static final String PROTOCOL_HANDLER_VARIABLE = 
+        "java.protocol.handler.pkgs";
+
+    public static final String Package = "org.apache.naming.resources";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLConnection.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLConnection.java
new file mode 100644
index 0000000..13b23d6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLConnection.java
@@ -0,0 +1,462 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+
+import org.apache.naming.JndiPermission;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+
+/**
+ * Connection to a JNDI directory context.
+ * <p/>
+ * Note: All the object attribute names are the WebDAV names, not the HTTP 
+ * names, so this class overrides some methods from URLConnection to do the
+ * queries using the right names. Content handler is also not used; the 
+ * content is directly returned.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class DirContextURLConnection 
+    extends URLConnection {
+    
+    
+    // ----------------------------------------------------------- Constructors
+    
+    
+    public DirContextURLConnection(DirContext context, URL url) {
+        super(url);
+        if (context == null)
+            throw new IllegalArgumentException
+                ("Directory context can't be null");
+        if (org.apache.naming.Constants.IS_SECURITY_ENABLED) {
+            this.permission = new JndiPermission(url.toString());
+    }
+        this.context = context;
+    }
+    
+    
+    // ----------------------------------------------------- Instance Variables
+    
+    
+    /**
+     * Directory context.
+     */
+    protected DirContext context;
+    
+    
+    /**
+     * Associated resource.
+     */
+    protected Resource resource;
+    
+    
+    /**
+     * Associated DirContext.
+     */
+    protected DirContext collection;
+    
+    
+    /**
+     * Other unknown object.
+     */
+    protected Object object;
+    
+    
+    /**
+     * Attributes.
+     */
+    protected Attributes attributes;
+    
+    
+    /**
+     * Date.
+     */
+    protected long date;
+    
+    
+    /**
+     * Permission
+     */
+    protected Permission permission;
+
+
+    // ------------------------------------------------------------- Properties
+    
+    
+    /**
+     * Connect to the DirContext, and retrieve the bound object, as well as
+     * its attributes. If no object is bound with the name specified in the
+     * URL, then an IOException is thrown.
+     * 
+     * @throws IOException Object not found
+     */
+    @Override
+    public void connect()
+        throws IOException {
+        
+        if (!connected) {
+            
+            try {
+                date = System.currentTimeMillis();
+                String path = getURL().getFile();
+                if (context instanceof ProxyDirContext) {
+                    ProxyDirContext proxyDirContext = 
+                        (ProxyDirContext) context;
+                    String hostName = proxyDirContext.getHostName();
+                    String contextPath = proxyDirContext.getContextPath();
+                    if (hostName != null) {
+                        if (!path.startsWith("/" + hostName + "/"))
+                            return;
+                        path = path.substring(hostName.length()+ 1);
+                    }
+                    if (contextPath != null) {
+                        if (!path.startsWith(contextPath + "/")) {
+                            return;
+                        }
+                        path = path.substring(contextPath.length());
+                    }
+                }
+                object = context.lookup(path);
+                attributes = context.getAttributes(path);
+                if (object instanceof Resource)
+                    resource = (Resource) object;
+                if (object instanceof DirContext)
+                    collection = (DirContext) object;
+            } catch (NamingException e) {
+                // Object not found
+            }
+            
+            connected = true;
+            
+        }
+        
+    }
+    
+    
+    /**
+     * Return the content length value.
+     */
+    @Override
+    public int getContentLength() {
+        return getHeaderFieldInt(ResourceAttributes.CONTENT_LENGTH, -1);
+    }
+    
+    
+    /**
+     * Return the content type value.
+     */
+    @Override
+    public String getContentType() {
+        return getHeaderField(ResourceAttributes.CONTENT_TYPE);
+    }
+    
+    
+    /**
+     * Return the last modified date.
+     */
+    @Override
+    public long getDate() {
+        return date;
+    }
+    
+    
+    /**
+     * Return the last modified date.
+     */
+    @Override
+    public long getLastModified() {
+
+        if (!connected) {
+            // Try to connect (silently)
+            try {
+                connect();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+
+        if (attributes == null)
+            return 0;
+
+        Attribute lastModified = 
+            attributes.get(ResourceAttributes.LAST_MODIFIED);
+        if (lastModified != null) {
+            try {
+                Date lmDate = (Date) lastModified.get();
+                return lmDate.getTime();
+            } catch (Exception e) {
+                // Ignore
+            }
+        }
+
+        return 0;
+    }
+    
+
+    protected String getHeaderValueAsString(Object headerValue) {
+        if (headerValue == null) return null;
+        if (headerValue instanceof Date) {
+            // return date strings (ie Last-Modified) in HTTP format, rather
+            // than Java format
+            return FastHttpDateFormat.formatDate(
+                    ((Date)headerValue).getTime(), null);
+        }
+        return headerValue.toString();
+    }
+
+
+    /**
+     * Returns an unmodifiable Map of the header fields.
+     */
+    @Override
+    public Map<String,List<String>> getHeaderFields() {
+
+      if (!connected) {
+          // Try to connect (silently)
+          try {
+              connect();
+          } catch (IOException e) {
+              //Ignore
+          }
+      }
+
+      if (attributes == null)
+          return (Collections.emptyMap());
+
+      HashMap<String,List<String>> headerFields =
+          new HashMap<String,List<String>>(attributes.size());
+      NamingEnumeration<String> attributeEnum = attributes.getIDs();
+      try {
+          while (attributeEnum.hasMore()) {
+              String attributeID = attributeEnum.next();
+              Attribute attribute = attributes.get(attributeID);
+              if (attribute == null) continue;
+              ArrayList<String> attributeValueList =
+                  new ArrayList<String>(attribute.size());
+              NamingEnumeration<?> attributeValues = attribute.getAll();
+              while (attributeValues.hasMore()) {
+                  Object attrValue = attributeValues.next();
+                  attributeValueList.add(getHeaderValueAsString(attrValue));
+              }
+              attributeValueList.trimToSize(); // should be a no-op if attribute.size() didn't lie
+              headerFields.put(attributeID, Collections.unmodifiableList(attributeValueList));
+          }
+      } catch (NamingException ne) {
+            // Shouldn't happen
+      }
+
+      return Collections.unmodifiableMap(headerFields);
+
+    }
+    
+    
+    /**
+     * Returns the name of the specified header field.
+     */
+    @Override
+    public String getHeaderField(String name) {
+
+        if (!connected) {
+            // Try to connect (silently)
+            try {
+                connect();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+        
+        if (attributes == null)
+            return (null);
+
+        NamingEnumeration<String> attributeEnum = attributes.getIDs();
+        try {
+            while (attributeEnum.hasMore()) {
+                String attributeID = attributeEnum.next();
+                if (attributeID.equalsIgnoreCase(name)) {
+                    Attribute attribute = attributes.get(attributeID);
+                    if (attribute == null) return null;
+                    Object attrValue = attribute.get(attribute.size()-1);
+                    return getHeaderValueAsString(attrValue);
+                }
+            }
+        } catch (NamingException ne) {
+            // Shouldn't happen
+        }
+
+        return (null);
+        
+    }
+    
+    
+    /**
+     * Get object content.
+     */
+    @Override
+    public Object getContent()
+        throws IOException {
+        
+        if (!connected)
+            connect();
+        
+        if (resource != null)
+            return getInputStream();
+        if (collection != null)
+            return collection;
+        if (object != null)
+            return object;
+        
+        throw new FileNotFoundException(
+                getURL() == null ? "null" : getURL().toString());
+        
+    }
+    
+    
+    /**
+     * Get object content.
+     */
+    @SuppressWarnings("rawtypes") // overridden method uses raw type Class[]
+    @Override
+    public Object getContent(Class[] classes)
+        throws IOException {
+        
+        Object obj = getContent();
+        
+        for (int i = 0; i < classes.length; i++) {
+            if (classes[i].isInstance(obj))
+                return obj;
+        }
+        
+        return null;
+        
+    }
+    
+    
+    /**
+     * Get input stream.
+     */
+    @Override
+    public InputStream getInputStream() 
+        throws IOException {
+        
+        if (!connected)
+            connect();
+        
+        if (resource == null) {
+            throw new FileNotFoundException(
+                    getURL() == null ? "null" : getURL().toString());
+        }
+
+        // Reopen resource
+        try {
+            resource = (Resource) context.lookup(getURL().getFile());
+        } catch (NamingException e) {
+            // Ignore
+        }
+        
+        return (resource.streamContent());
+        
+    }
+    
+    
+    /**
+     * Get the Permission for this URL
+     */
+    @Override
+    public Permission getPermission() {
+
+        return permission;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+    
+    
+    /**
+     * List children of this collection. The names given are relative to this
+     * URI's path. The full uri of the children is then : path + "/" + name.
+     */
+    public Enumeration<String> list()
+        throws IOException {
+        
+        if (!connected) {
+            connect();
+        }
+        
+        if ((resource == null) && (collection == null)) {
+            throw new FileNotFoundException(
+                    getURL() == null ? "null" : getURL().toString());
+        }
+        
+        Vector<String> result = new Vector<String>();
+        
+        if (collection != null) {
+            try {
+                String file = getURL().getFile();
+                // This will be of the form /<hostname>/<contextpath>/file name
+                // if <contextpath> is not empty otherwise this will be of the
+                // form /<hostname>/file name
+                // Strip off the hostname and the contextpath
+                int start;
+                if(context instanceof ProxyDirContext &&
+                        "".equals(((ProxyDirContext)context).getContextPath())){
+                    start = file.indexOf('/',1);
+                }
+                else
+                    start = file.indexOf('/', file.indexOf('/', 1) + 1);
+                
+                NamingEnumeration<NameClassPair> enumeration =
+                    context.list(file.substring(start));
+                while (enumeration.hasMoreElements()) {
+                    NameClassPair ncp = enumeration.nextElement();
+                    result.addElement(ncp.getName());
+                }
+            } catch (NamingException e) {
+                // Unexpected exception
+                throw new FileNotFoundException(
+                        getURL() == null ? "null" : getURL().toString());
+            }
+        }
+        
+        return result.elements();
+        
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLStreamHandler.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLStreamHandler.java
new file mode 100644
index 0000000..c7cfb31
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLStreamHandler.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Hashtable;
+
+import javax.naming.directory.DirContext;
+
+/**
+ * Stream handler to a JNDI directory context.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class DirContextURLStreamHandler extends URLStreamHandler {
+    
+    
+    // ----------------------------------------------------------- Constructors
+    
+    
+    public DirContextURLStreamHandler() {
+        // NOOP
+    }
+    
+    
+    public DirContextURLStreamHandler(DirContext context) {
+        this.context = context;
+    }
+    
+    
+    // -------------------------------------------------------------- Variables
+    
+    
+    /**
+     * Bindings class loader - directory context. Keyed by CL id.
+     */
+    private static Hashtable<ClassLoader,DirContext> clBindings =
+        new Hashtable<ClassLoader,DirContext>();
+    
+    
+    /**
+     * Bindings thread - directory context. Keyed by thread id.
+     */
+    private static Hashtable<Thread,DirContext> threadBindings =
+        new Hashtable<Thread,DirContext>();
+    
+    
+    // ----------------------------------------------------- Instance Variables
+    
+    
+    /**
+     * Directory context.
+     */
+    protected DirContext context = null;
+    
+    
+    // ------------------------------------------------------------- Properties
+    
+    
+    // ----------------------------------------------- URLStreamHandler Methods
+    
+    
+    /**
+     * Opens a connection to the object referenced by the <code>URL</code> 
+     * argument.
+     */
+    @Override
+    protected URLConnection openConnection(URL u) 
+        throws IOException {
+        DirContext currentContext = this.context;
+        if (currentContext == null)
+            currentContext = get();
+        return new DirContextURLConnection(currentContext, u);
+    }
+    
+    
+    // ------------------------------------------------------------ URL Methods
+    
+    
+    /**
+     * Override as part of the fix for 36534, to ensure toString is correct.
+     */
+    @Override
+    protected String toExternalForm(URL u) {
+        // pre-compute length of StringBuilder
+        int len = u.getProtocol().length() + 1;
+        if (u.getPath() != null) {
+            len += u.getPath().length();
+        }
+        if (u.getQuery() != null) {
+            len += 1 + u.getQuery().length();
+        }
+        if (u.getRef() != null) 
+            len += 1 + u.getRef().length();
+        StringBuilder result = new StringBuilder(len);
+        result.append(u.getProtocol());
+        result.append(":");
+        if (u.getPath() != null) {
+            result.append(u.getPath());
+        }
+        if (u.getQuery() != null) {
+            result.append('?');
+            result.append(u.getQuery());
+        }
+        if (u.getRef() != null) {
+            result.append("#");
+            result.append(u.getRef());
+        }
+        return result.toString();
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+    
+    
+    /**
+     * Set the java.protocol.handler.pkgs system property. For use when
+     * embedding Tomcat and the embedding application has already set its own
+     * {@link java.net.URLStreamHandlerFactory}.
+     */
+    public static void setProtocolHandler() {
+        String value = System.getProperty(Constants.PROTOCOL_HANDLER_VARIABLE);
+        if (value == null) {
+            value = Constants.Package;
+            System.setProperty(Constants.PROTOCOL_HANDLER_VARIABLE, value);
+        } else if (value.indexOf(Constants.Package) == -1) {
+            value += "|" + Constants.Package;
+            System.setProperty(Constants.PROTOCOL_HANDLER_VARIABLE, value);
+        }
+    }
+    
+    
+    /**
+     * Returns true if the thread or the context class loader of the current 
+     * thread is bound.
+     */
+    public static boolean isBound() {
+        return (clBindings.containsKey
+                (Thread.currentThread().getContextClassLoader()))
+            || (threadBindings.containsKey(Thread.currentThread()));
+    }
+    
+    
+    /**
+     * Binds a directory context to a class loader.
+     */
+    public static void bind(DirContext dirContext) {
+        ClassLoader currentCL = 
+            Thread.currentThread().getContextClassLoader();
+        if (currentCL != null)
+            clBindings.put(currentCL, dirContext);
+    }
+    
+    
+    /**
+     * Unbinds a directory context to a class loader.
+     */
+    public static void unbind() {
+        ClassLoader currentCL = 
+            Thread.currentThread().getContextClassLoader();
+        if (currentCL != null)
+            clBindings.remove(currentCL);
+    }
+    
+    
+    /**
+     * Binds a directory context to a thread.
+     */
+    public static void bindThread(DirContext dirContext) {
+        threadBindings.put(Thread.currentThread(), dirContext);
+    }
+    
+    
+    /**
+     * Unbinds a directory context to a thread.
+     */
+    public static void unbindThread() {
+        threadBindings.remove(Thread.currentThread());
+    }
+    
+    
+    /**
+     * Get the bound context.
+     */
+    public static DirContext get() {
+
+        DirContext result = null;
+
+        Thread currentThread = Thread.currentThread();
+        ClassLoader currentCL = currentThread.getContextClassLoader();
+
+        // Checking CL binding
+        result = clBindings.get(currentCL);
+        if (result != null)
+            return result;
+
+        // Checking thread biding
+        result = threadBindings.get(currentThread);
+
+        // Checking parent CL binding
+        currentCL = currentCL.getParent();
+        while (currentCL != null) {
+            result = clBindings.get(currentCL);
+            if (result != null)
+                return result;
+            currentCL = currentCL.getParent();
+        }
+
+        if (result == null)
+            throw new IllegalStateException("Illegal class loader binding");
+
+        return result;
+
+    }
+    
+    
+    /**
+     * Binds a directory context to a class loader.
+     */
+    public static void bind(ClassLoader cl, DirContext dirContext) {
+        clBindings.put(cl, dirContext);
+    }
+    
+    
+    /**
+     * Unbinds a directory context to a class loader.
+     */
+    public static void unbind(ClassLoader cl) {
+        clBindings.remove(cl);
+    }
+    
+    
+    /**
+     * Get the bound context.
+     */
+    public static DirContext get(ClassLoader cl) {
+        return clBindings.get(cl);
+    }
+    
+    
+    /**
+     * Get the bound context.
+     */
+    public static DirContext get(Thread thread) {
+        return threadBindings.get(thread);
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLStreamHandlerFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLStreamHandlerFactory.java
new file mode 100644
index 0000000..3e903f0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/DirContextURLStreamHandlerFactory.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Factory for Stream handlers to a JNDI directory context that also supports
+ * users specifying additional stream handler.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class DirContextURLStreamHandlerFactory
+        implements URLStreamHandlerFactory {
+    
+    // Singleton
+    private static DirContextURLStreamHandlerFactory instance =
+        new DirContextURLStreamHandlerFactory();
+
+    public static DirContextURLStreamHandlerFactory getInstance() {
+        return instance;
+    }
+
+    public static void addUserFactory(URLStreamHandlerFactory factory) {
+        instance.userFactories.add(factory);
+    }
+
+
+    private List<URLStreamHandlerFactory> userFactories =
+        new CopyOnWriteArrayList<URLStreamHandlerFactory>();
+
+    private DirContextURLStreamHandlerFactory() {
+        // Hide the default constructor
+    }
+    
+    
+    /**
+     * Creates a new URLStreamHandler instance with the specified protocol.
+     * Will return null if the protocol is not <code>jndi</code>.
+     * 
+     * @param protocol the protocol (must be "jndi" here)
+     * @return a URLStreamHandler for the jndi protocol, or null if the 
+     * protocol is not JNDI
+     */
+    @Override
+    public URLStreamHandler createURLStreamHandler(String protocol) {
+        if (protocol.equals("jndi")) {
+            return new DirContextURLStreamHandler();
+        } else {
+            for (URLStreamHandlerFactory factory : userFactories) {
+                URLStreamHandler handler =
+                    factory.createURLStreamHandler(protocol);
+                if (handler != null) {
+                    return handler;
+                }
+            }
+            return null;
+        }
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/FileDirContext.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/FileDirContext.java
new file mode 100644
index 0000000..791d529
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/FileDirContext.java
@@ -0,0 +1,1109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.naming.resources;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.naming.NameAlreadyBoundException;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.OperationNotSupportedException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.apache.catalina.util.RequestUtil;
+import org.apache.naming.NamingContextEnumeration;
+import org.apache.naming.NamingEntry;
+
+/**
+ * Filesystem Directory Context implementation helper class.
+ *
+ * @author Remy Maucherat
+ * @version $Id: FileDirContext.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+
+public class FileDirContext extends BaseDirContext {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( FileDirContext.class );
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     * The descriptive information string for this implementation.
+     */
+    protected static final int BUFFER_SIZE = 2048;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Builds a file directory context using the given environment.
+     */
+    public FileDirContext() {
+        super();
+    }
+
+
+    /**
+     * Builds a file directory context using the given environment.
+     */
+    public FileDirContext(Hashtable<String,Object> env) {
+        super(env);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The document base directory.
+     */
+    protected File base = null;
+
+
+    /**
+     * Absolute normalized filename of the base.
+     */
+    protected String absoluteBase = null;
+
+
+    /**
+     * Allow linking.
+     */
+    protected boolean allowLinking = false;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the document root.
+     *
+     * @param docBase The new document root
+     *
+     * @exception IllegalArgumentException if the specified value is not
+     *  supported by this implementation
+     * @exception IllegalArgumentException if this would create a
+     *  malformed URL
+     */
+    @Override
+    public void setDocBase(String docBase) {
+
+    // Validate the format of the proposed document root
+    if (docBase == null)
+        throw new IllegalArgumentException
+        (sm.getString("resources.null"));
+
+    // Calculate a File object referencing this document base directory
+    base = new File(docBase);
+        try {
+            base = base.getCanonicalFile();
+        } catch (IOException e) {
+            // Ignore
+        }
+
+    // Validate that the document base is an existing directory
+    if (!base.exists() || !base.isDirectory() || !base.canRead())
+        throw new IllegalArgumentException
+        (sm.getString("fileResources.base", docBase));
+        this.absoluteBase = base.getAbsolutePath();
+        super.setDocBase(docBase);
+
+    }
+
+
+    /**
+     * Set allow linking.
+     */
+    public void setAllowLinking(boolean allowLinking) {
+        this.allowLinking = allowLinking;
+    }
+
+
+    /**
+     * Is linking allowed.
+     */
+    public boolean getAllowLinking() {
+        return allowLinking;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Release any resources allocated for this directory context.
+     */
+    @Override
+    public void release() {
+        super.release();
+    }
+
+
+    /**
+     * Return the real path for a given virtual path, if possible; otherwise
+     * return <code>null</code>.
+     *
+     * @param path The path to the desired resource
+     */
+    @Override
+    protected String doGetRealPath(String path) {
+        File file = new File(getDocBase(), path);
+        return file.getAbsolutePath();
+    }
+
+    // -------------------------------------------------------- Context Methods
+
+
+    /**
+     * Retrieves the named object.
+     *
+     * @param name the name of the object to look up
+     * @return the object bound to name
+     */
+    @Override
+    protected Object doLookup(String name) {
+        Object result = null;
+        File file = file(name);
+
+        if (file == null)
+            return null;
+
+        if (file.isDirectory()) {
+            FileDirContext tempContext = new FileDirContext(env);
+            tempContext.setDocBase(file.getPath());
+            tempContext.setAllowLinking(getAllowLinking());
+            result = tempContext;
+        } else {
+            result = new FileResource(file);
+        }
+
+        return result;
+
+    }
+
+
+    /**
+     * Unbinds the named object. Removes the terminal atomic name in name
+     * from the target context--that named by all but the terminal atomic
+     * part of name.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic
+     * name is not bound in the target context, but throws
+     * NameNotFoundException if any of the intermediate contexts do not exist.
+     *
+     * @param name the name to bind; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not
+     * exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void unbind(String name)
+        throws NamingException {
+
+        File file = file(name);
+
+        if (file == null)
+            throw new NameNotFoundException(
+                    sm.getString("resources.notFound", name));
+
+        if (!file.delete())
+            throw new NamingException
+                (sm.getString("resources.unbindFailed", name));
+
+    }
+
+
+    /**
+     * Binds a new name to the object bound to an old name, and unbinds the
+     * old name. Both names are relative to this context. Any attributes
+     * associated with the old name become associated with the new name.
+     * Intermediate contexts of the old name are not changed.
+     *
+     * @param oldName the name of the existing binding; may not be empty
+     * @param newName the name of the new binding; may not be empty
+     * @exception NameAlreadyBoundException if newName is already bound
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rename(String oldName, String newName)
+        throws NamingException {
+
+        File file = file(oldName);
+
+        if (file == null)
+            throw new NameNotFoundException
+                (sm.getString("resources.notFound", oldName));
+
+        File newFile = new File(base, newName);
+
+        if (!file.renameTo(newFile)) {
+            throw new NamingException(sm.getString("resources.renameFail",
+                    oldName, newName));
+        }
+
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the class
+     * names of objects bound to them. The contents of any subcontexts are
+     * not included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on
+     * an enumeration previously returned is undefined.
+     *
+     * @param name the name of the context to list
+     * @return an enumeration of the names and class names of the bindings in
+     * this context. Each element of the enumeration is of type NameClassPair.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<NameClassPair> list(String name)
+        throws NamingException {
+
+        File file = file(name);
+
+        if (file == null)
+            throw new NameNotFoundException
+                (sm.getString("resources.notFound", name));
+
+        return new NamingContextEnumeration(list(file).iterator());
+
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the
+     * objects bound to them. The contents of any subcontexts are not
+     * included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on
+     * an enumeration previously returned is undefined.
+     *
+     * @param name the name of the context to list
+     * @return an enumeration of the bindings in this context.
+     * Each element of the enumeration is of type Binding.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    protected List<NamingEntry> doListBindings(String name)
+        throws NamingException {
+
+        File file = file(name);
+
+        if (file == null)
+            return null;
+        
+        return list(file);
+
+    }
+
+
+    /**
+     * Destroys the named context and removes it from the namespace. Any
+     * attributes associated with the name are also removed. Intermediate
+     * contexts are not destroyed.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic
+     * name is not bound in the target context, but throws
+     * NameNotFoundException if any of the intermediate contexts do not exist.
+     *
+     * In a federated naming system, a context from one naming system may be
+     * bound to a name in another. One can subsequently look up and perform
+     * operations on the foreign context using a composite name. However, an
+     * attempt destroy the context using this composite name will fail with
+     * NotContextException, because the foreign context is not a "subcontext"
+     * of the context in which it is bound. Instead, use unbind() to remove
+     * the binding of the foreign context. Destroying the foreign context
+     * requires that the destroySubcontext() be performed on a context from
+     * the foreign context's "native" naming system.
+     *
+     * @param name the name of the context to be destroyed; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not
+     * exist
+     * @exception javax.naming.NotContextException if the name is bound but does
+     * not name a context, or does not name a context of the appropriate type
+     */
+    @Override
+    public void destroySubcontext(String name)
+        throws NamingException {
+        unbind(name);
+    }
+
+
+    /**
+     * Retrieves the named object, following links except for the terminal
+     * atomic component of the name. If the object bound to name is not a
+     * link, returns the object itself.
+     *
+     * @param name the name of the object to look up
+     * @return the object bound to name, not following the terminal link
+     * (if any).
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object lookupLink(String name)
+        throws NamingException {
+        // Note : Links are not supported
+        return lookup(name);
+    }
+
+
+    /**
+     * Retrieves the full name of this context within its own namespace.
+     * <p>
+     * Many naming services have a notion of a "full name" for objects in
+     * their respective namespaces. For example, an LDAP entry has a
+     * distinguished name, and a DNS record has a fully qualified name. This
+     * method allows the client application to retrieve this name. The string
+     * returned by this method is not a JNDI composite name and should not be
+     * passed directly to context methods. In naming systems for which the
+     * notion of full name does not make sense,
+     * OperationNotSupportedException is thrown.
+     *
+     * @return this context's name in its own namespace; never null
+     * @exception OperationNotSupportedException if the naming system does
+     * not have the notion of a full name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public String getNameInNamespace()
+        throws NamingException {
+        return docBase;
+    }
+
+
+    // ----------------------------------------------------- DirContext Methods
+
+
+    /**
+     * Retrieves selected attributes associated with a named object.
+     * See the class description regarding attribute models, attribute type
+     * names, and operational attributes.
+     *
+     * @return the requested attributes; never null
+     * @param name the name of the object from which to retrieve attributes
+     * @param attrIds the identifiers of the attributes to retrieve. null
+     * indicates that all attributes should be retrieved; an empty array
+     * indicates that none should be retrieved
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    protected Attributes doGetAttributes(String name, String[] attrIds)
+        throws NamingException {
+
+        // Building attribute list
+        File file = file(name);
+
+        if (file == null)
+            return null;
+
+        return new FileResourceAttributes(file);
+
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object. The order of
+     * the modifications is not specified. Where possible, the modifications
+     * are performed atomically.
+     *
+     * @param name the name of the object whose attributes will be updated
+     * @param mod_op the modification operation, one of: ADD_ATTRIBUTE,
+     * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
+     * @param attrs the attributes to be used for the modification; may not
+     * be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(String name, int mod_op, Attributes attrs)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object using an an
+     * ordered list of modifications. The modifications are performed in the
+     * order specified. Each modification specifies a modification operation
+     * code and an attribute on which to operate. Where possible, the
+     * modifications are performed atomically.
+     *
+     * @param name the name of the object whose attributes will be updated
+     * @param mods an ordered sequence of modifications to be performed; may
+     * not be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(String name, ModificationItem[] mods)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes. If attrs
+     * is null, the resulting binding will have the attributes associated
+     * with obj if obj is a DirContext, and no attributes otherwise. If attrs
+     * is non-null, the resulting binding will have attrs as its attributes;
+     * any attributes associated with obj are ignored.
+     *
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception NameAlreadyBoundException if name is already bound
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(String name, Object obj, Attributes attrs)
+        throws NamingException {
+
+        // Note: No custom attributes allowed
+
+        File file = new File(base, name);
+        if (file.exists())
+            throw new NameAlreadyBoundException
+                (sm.getString("resources.alreadyBound", name));
+
+        rebind(name, obj, attrs);
+
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes,
+     * overwriting any existing binding. If attrs is null and obj is a
+     * DirContext, the attributes from obj are used. If attrs is null and obj
+     * is not a DirContext, any existing attributes associated with the object
+     * already bound in the directory remain unchanged. If attrs is non-null,
+     * any existing attributes associated with the object already bound in
+     * the directory are removed and attrs is associated with the named
+     * object. If obj is a DirContext and attrs is non-null, the attributes
+     * of obj are ignored.
+     *
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(String name, Object obj, Attributes attrs)
+        throws NamingException {
+
+        // Note: No custom attributes allowed
+        // Check obj type
+
+        File file = new File(base, name);
+
+        InputStream is = null;
+        if (obj instanceof Resource) {
+            try {
+                is = ((Resource) obj).streamContent();
+            } catch (IOException e) {
+                // Ignore
+            }
+        } else if (obj instanceof InputStream) {
+            is = (InputStream) obj;
+        } else if (obj instanceof DirContext) {
+            if (file.exists()) {
+                if (!file.delete())
+                    throw new NamingException
+                        (sm.getString("resources.bindFailed", name));
+            }
+            if (!file.mkdir())
+                throw new NamingException
+                    (sm.getString("resources.bindFailed", name));
+        }
+        if (is == null)
+            throw new NamingException
+                (sm.getString("resources.bindFailed", name));
+
+        // Open os
+
+        try {
+            FileOutputStream os = null;
+            byte buffer[] = new byte[BUFFER_SIZE];
+            int len = -1;
+            try {
+                os = new FileOutputStream(file);
+                while (true) {
+                    len = is.read(buffer);
+                    if (len == -1)
+                        break;
+                    os.write(buffer, 0, len);
+                }
+            } finally {
+                if (os != null)
+                    os.close();
+                is.close();
+            }
+        } catch (IOException e) {
+            NamingException ne = new NamingException
+                    (sm.getString("resources.bindFailed", e));
+            ne.initCause(e);
+            throw ne;
+        }
+
+    }
+
+
+    /**
+     * Creates and binds a new context, along with associated attributes.
+     * This method creates a new subcontext with the given name, binds it in
+     * the target context (that named by all but terminal atomic component of
+     * the name), and associates the supplied attributes with the newly
+     * created object. All intermediate and target contexts must already
+     * exist. If attrs is null, this method is equivalent to
+     * Context.createSubcontext().
+     *
+     * @param name the name of the context to create; may not be empty
+     * @param attrs the attributes to associate with the newly created context
+     * @return the newly created context
+     * @exception NameAlreadyBoundException if the name is already bound
+     * @exception javax.naming.directory.InvalidAttributesException if attrs
+     * does not contain all the mandatory attributes required for creation
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext createSubcontext(String name, Attributes attrs)
+        throws NamingException {
+
+        File file = new File(base, name);
+        if (file.exists())
+            throw new NameAlreadyBoundException
+                (sm.getString("resources.alreadyBound", name));
+        if (!file.mkdir())
+            throw new NamingException
+                (sm.getString("resources.bindFailed", name));
+        return (DirContext) lookup(name);
+
+    }
+
+
+    /**
+     * Retrieves the schema associated with the named object. The schema
+     * describes rules regarding the structure of the namespace and the
+     * attributes stored within it. The schema specifies what types of
+     * objects can be added to the directory and where they can be added;
+     * what mandatory and optional attributes an object can have. The range
+     * of support for schemas is directory-specific.
+     *
+     * @param name the name of the object whose schema is to be retrieved
+     * @return the schema associated with the context; never null
+     * @exception OperationNotSupportedException if schema not supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchema(String name)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Retrieves a context containing the schema objects of the named
+     * object's class definitions.
+     *
+     * @param name the name of the object whose object class definition is to
+     * be retrieved
+     * @return the DirContext containing the named object's class
+     * definitions; never null
+     * @exception OperationNotSupportedException if schema not supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchemaClassDefinition(String name)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set
+     * of attributes, and retrieves selected attributes. The search is
+     * performed using the default SearchControls settings.
+     *
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or
+     * null, all objects in the target context are returned.
+     * @param attributesToReturn the attributes to return. null indicates
+     * that all attributes are to be returned; an empty array indicates that
+     * none are to be returned.
+     * @return a non-null enumeration of SearchResult objects. Each
+     * SearchResult contains the attributes identified by attributesToReturn
+     * and the name of the corresponding object, named relative to the
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            Attributes matchingAttributes, String[] attributesToReturn)
+        throws NamingException {
+        return null;
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set
+     * of attributes. This method returns all the attributes of such objects.
+     * It is equivalent to supplying null as the atributesToReturn parameter
+     * to the method search(Name, Attributes, String[]).
+     *
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or
+     * null, all objects in the target context are returned.
+     * @return a non-null enumeration of SearchResult objects. Each
+     * SearchResult contains the attributes identified by attributesToReturn
+     * and the name of the corresponding object, named relative to the
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            Attributes matchingAttributes) throws NamingException {
+        return null;
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the
+     * given search filter. Performs the search as specified by the search
+     * controls.
+     *
+     * @param name the name of the context or object to search
+     * @param filter the filter expression to use for the search; may not be
+     * null
+     * @param cons the search controls that control the search. If null,
+     * the default search controls are used (equivalent to
+     * (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy
+     * the filter; never null
+     * @exception javax.naming.directory.InvalidSearchFilterException if the
+     * search filter specified is not supported or understood by the underlying
+     * directory
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name, String filter,
+                                    SearchControls cons)
+        throws NamingException {
+        return null;
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the
+     * given search filter. Performs the search as specified by the search
+     * controls.
+     *
+     * @param name the name of the context or object to search
+     * @param filterExpr the filter expression to use for the search.
+     * The expression may contain variables of the form "{i}" where i is a
+     * nonnegative integer. May not be null.
+     * @param filterArgs the array of arguments to substitute for the
+     * variables in filterExpr. The value of filterArgs[i] will replace each
+     * occurrence of "{i}". If null, equivalent to an empty array.
+     * @param cons the search controls that control the search. If null, the
+     * default search controls are used (equivalent to (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy the
+     * filter; never null
+     * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i}
+     * expressions where i is outside the bounds of the array filterArgs
+     * @exception javax.naming.directory.InvalidSearchControlsException if cons
+     * contains invalid settings
+     * @exception javax.naming.directory.InvalidSearchFilterException if
+     * filterExpr with filterArgs represents an invalid search filter
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            String filterExpr, Object[] filterArgs, SearchControls cons)
+        throws NamingException {
+        return null;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return a context-relative path, beginning with a "/", that represents
+     * the canonical version of the specified path after ".." and "." elements
+     * are resolved out.  If the specified path attempts to go outside the
+     * boundaries of the current context (i.e. too many ".." path elements
+     * are present), return <code>null</code> instead.
+     *
+     * @param path Path to be normalized
+     */
+    protected String normalize(String path) {
+
+        return RequestUtil.normalize(path, File.separatorChar == '\\');
+
+    }
+
+    /**
+     * Return a File object representing the specified normalized
+     * context-relative path if it exists and is readable.  Otherwise,
+     * return <code>null</code>.
+     *
+     * @param name Normalized context-relative path (with leading '/')
+     */
+    protected File file(String name) {
+
+        File file = new File(base, name);
+        if (file.exists() && file.canRead()) {
+
+            if (allowLinking)
+                return file;
+            
+            // Check that this file belongs to our root path
+            String canPath = null;
+            try {
+                canPath = file.getCanonicalPath();
+            } catch (IOException e) {
+                // Ignore
+            }
+            if (canPath == null)
+                return null;
+
+            // Check to see if going outside of the web application root
+            if (!canPath.startsWith(absoluteBase)) {
+                return null;
+            }
+
+            // Case sensitivity check - this is now always done
+            String fileAbsPath = file.getAbsolutePath();
+            if (fileAbsPath.endsWith("."))
+                fileAbsPath = fileAbsPath + "/";
+            String absPath = normalize(fileAbsPath);
+            canPath = normalize(canPath);
+            if ((absoluteBase.length() < absPath.length())
+                && (absoluteBase.length() < canPath.length())) {
+                absPath = absPath.substring(absoluteBase.length() + 1);
+                if (absPath == null)
+                    return null;
+                if (absPath.equals(""))
+                    absPath = "/";
+                canPath = canPath.substring(absoluteBase.length() + 1);
+                if (canPath.equals(""))
+                    canPath = "/";
+                if (!canPath.equals(absPath))
+                    return null;
+            }
+
+        } else {
+            return null;
+        }
+        return file;
+
+    }
+
+
+    /**
+     * List the resources which are members of a collection.
+     *
+     * @param file Collection
+     * @return Vector containing NamingEntry objects
+     */
+    protected List<NamingEntry> list(File file) {
+
+        List<NamingEntry> entries = new ArrayList<NamingEntry>();
+        if (!file.isDirectory())
+            return entries;
+        String[] names = file.list();
+        if (names==null) {
+            /* Some IO error occurred such as bad file permissions.
+               Prevent a NPE with Arrays.sort(names) */
+            log.warn(sm.getString("fileResources.listingNull",
+                                  file.getAbsolutePath()));
+            return entries;
+        }
+
+        Arrays.sort(names);             // Sort alphabetically
+        NamingEntry entry = null;
+
+        for (int i = 0; i < names.length; i++) {
+
+            File currentFile = new File(file, names[i]);
+            Object object = null;
+            if (currentFile.isDirectory()) {
+                FileDirContext tempContext = new FileDirContext(env);
+                tempContext.setDocBase(file.getPath());
+                tempContext.setAllowLinking(getAllowLinking());
+                object = tempContext;
+            } else {
+                object = new FileResource(currentFile);
+            }
+            entry = new NamingEntry(names[i], object, NamingEntry.ENTRY);
+            entries.add(entry);
+
+        }
+
+        return entries;
+
+    }
+
+
+    // ----------------------------------------------- FileResource Inner Class
+
+
+    /**
+     * This specialized resource implementation avoids opening the InputStream
+     * to the file right away (which would put a lock on the file).
+     */
+    protected static class FileResource extends Resource {
+
+
+        // -------------------------------------------------------- Constructor
+
+
+        public FileResource(File file) {
+            this.file = file;
+        }
+
+
+        // --------------------------------------------------- Member Variables
+
+
+        /**
+         * Associated file object.
+         */
+        protected File file;
+
+
+        // --------------------------------------------------- Resource Methods
+
+
+        /**
+         * Content accessor.
+         *
+         * @return InputStream
+         */
+        @Override
+        public InputStream streamContent()
+            throws IOException {
+            if (binaryContent == null) {
+                FileInputStream fis = new FileInputStream(file);
+                inputStream = fis;
+                return fis;
+            }
+            return super.streamContent();
+        }
+
+
+    }
+
+
+    // ------------------------------------- FileResourceAttributes Inner Class
+
+
+    /**
+     * This specialized resource attribute implementation does some lazy
+     * reading (to speed up simple checks, like checking the last modified
+     * date).
+     */
+    protected static class FileResourceAttributes extends ResourceAttributes {
+
+        private static final long serialVersionUID = 1L;
+
+        // -------------------------------------------------------- Constructor
+
+        public FileResourceAttributes(File file) {
+            this.file = file;
+            getCreation();
+            getLastModified();
+        }
+
+        // --------------------------------------------------- Member Variables
+
+
+        protected File file;
+
+
+        protected boolean accessed = false;
+
+
+        protected String canonicalPath = null;
+
+
+        // ----------------------------------------- ResourceAttributes Methods
+
+
+        /**
+         * Is collection.
+         */
+        @Override
+        public boolean isCollection() {
+            if (!accessed) {
+                collection = file.isDirectory();
+                accessed = true;
+            }
+            return super.isCollection();
+        }
+
+
+        /**
+         * Get content length.
+         *
+         * @return content length value
+         */
+        @Override
+        public long getContentLength() {
+            if (contentLength != -1L)
+                return contentLength;
+            contentLength = file.length();
+            return contentLength;
+        }
+
+
+        /**
+         * Get creation time.
+         *
+         * @return creation time value
+         */
+        @Override
+        public long getCreation() {
+            if (creation != -1L)
+                return creation;
+            creation = getLastModified();
+            return creation;
+        }
+
+
+        /**
+         * Get creation date.
+         *
+         * @return Creation date value
+         */
+        @Override
+        public Date getCreationDate() {
+            if (creation == -1L) {
+                creation = getCreation();
+            }
+            return super.getCreationDate();
+        }
+
+
+        /**
+         * Get last modified time.
+         *
+         * @return lastModified time value
+         */
+        @Override
+        public long getLastModified() {
+            if (lastModified != -1L)
+                return lastModified;
+            lastModified = file.lastModified();
+            return lastModified;
+        }
+
+
+        /**
+         * Get lastModified date.
+         *
+         * @return LastModified date value
+         */
+        @Override
+        public Date getLastModifiedDate() {
+            if (lastModified == -1L) {
+                lastModified = getLastModified();
+            }
+            return super.getLastModifiedDate();
+        }
+
+
+        /**
+         * Get name.
+         *
+         * @return Name value
+         */
+        @Override
+        public String getName() {
+            if (name == null)
+                name = file.getName();
+            return name;
+        }
+
+
+        /**
+         * Get resource type.
+         *
+         * @return String resource type
+         */
+        @Override
+        public String getResourceType() {
+            if (!accessed) {
+                collection = file.isDirectory();
+                accessed = true;
+            }
+            return super.getResourceType();
+        }
+
+        
+        /**
+         * Get canonical path.
+         * 
+         * @return String the file's canonical path
+         */
+        @Override
+        public String getCanonicalPath() {
+            if (canonicalPath == null) {
+                try {
+                    canonicalPath = file.getCanonicalPath();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+            return canonicalPath;
+        }
+        
+
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/ImmutableNameNotFoundException.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ImmutableNameNotFoundException.java
new file mode 100644
index 0000000..071c9fa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ImmutableNameNotFoundException.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import javax.naming.Name;
+import javax.naming.NameNotFoundException;
+
+/**
+ * Immutable exception to avoid useless object creation by the proxy context.
+ * This should be used only by the proxy context. Actual contexts should return
+ * properly populated exceptions.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class ImmutableNameNotFoundException
+    extends NameNotFoundException {
+
+    private static final long serialVersionUID = 1L;
+    
+    @Override
+    public void appendRemainingComponent(String name) {/*NOOP*/}
+    @Override
+    public void appendRemainingName(Name name) {/*NOOP*/}
+    @Override
+    public void setRemainingName(Name name) {/*NOOP*/}
+    @Override
+    public void setResolvedName(Name name) {/*NOOP*/}
+    @Override
+    public void setRootCause(Throwable e) {/*NOOP*/}
+
+    @Override
+    public synchronized Throwable fillInStackTrace() {
+        // This class does not provide a stack trace
+        return this;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings.properties
new file mode 100644
index 0000000..4cc7cff
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings.properties
@@ -0,0 +1,45 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+fileResources.base=Document base {0} does not exist or is not a readable directory
+fileResources.listingNull=Could not get dir listing for {0}
+warResources.notWar=Doc base must point to a WAR file
+warResources.invalidWar=Invalid or unreadable WAR file : {0}
+jarResources.syntax=Document base {0} must start with ''jar:'' and end with ''!/''
+resources.addResourcesJarFail=Failed to add resources jar [{0}]
+resources.alreadyStarted=Resources has already been started
+resources.connect=Cannot connect to document base {0}
+resources.input=Cannot create input stream for resource {0}
+resources.invalidCache=Unable to create a resource cache of type [{0}]
+resources.notStarted=Resources has not yet been started
+resources.null=Document base cannot be null
+resources.notFound=Resource {0} not found
+resources.path=Context relative path {0} must start with ''/''
+resources.renameFail=Failed to rename [{0}] to [{1}]
+resources.alreadyBound=Name {0} is already bound in this Context
+resources.bindFailed=Bind failed: {0}
+resources.unbindFailed=Unbind failed: {0}
+resources.invalidAliasPath=The alias path ''{0}'' must start with ''/''
+resources.invalidAliasMapping=The alias mapping ''{0}'' is not valid
+resources.invalidAliasNotAllowed=The alias location ''{0}'' is not allowed
+resources.invalidAliasNotExist=The alias location ''{0}'' does not exist
+resources.invalidAliasFile=The alias location ''{0}'' points to a file that is not a WAR file
+resources.invalidName=The name [{0}] is not valid
+standardResources.alreadyStarted=Resources has already been started
+standardResources.directory=File base {0} is not a directory
+standardResources.exists=File base {0} does not exist
+standardResources.notStarted=Resources has not yet been started
+standardResources.null=Document base cannot be null
+standardResources.slash=Document base {0} must not end with a slash
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_es.properties
new file mode 100644
index 0000000..92fa505
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_es.properties
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+fileResources.base = El Documento base {0} no existe o no es un directorio legible
+fileResources.listingNull = No pude obtener lista de directorio para {0}
+warResources.notWar = Doc base debe de apuntar a un archivo WAR
+warResources.invalidWar = Archivo WAR inv\u00E1lido o ilegible\: {0}
+jarResources.syntax = Documento base {0} debe de empezar con ''jar\:'' y acabar con ''\!/''
+resources.alreadyStarted = Ya han sido arrancados los Recursos
+resources.connect = No puedo conectar a documento base {0}
+resources.input = No puedo crear flujo (stream) de entrada para recurso {0}
+resources.notStarted = A\u00FAn no han sido arrancados los Recursos
+resources.null = El Documento base no puede ser nulo
+resources.notFound = Recurso {0} no hallado
+resources.path = Trayectoria relativa a contexto {0} debe de comenzar con ''/''
+resources.alreadyBound = El Nombre {0} ya ha sido cambiado (bound) en este Contexto
+resources.bindFailed = Fall\u00F3 el Cambio (Bind)\: {0}
+resources.unbindFailed = Fall\u00F3 el Descambio (Unbind)\: {0}
+standardResources.alreadyStarted = Ya han sido arrancados los Recursos
+standardResources.directory = El archivo base {0} no es un directorio
+standardResources.exists = El archivo base {0} no existe
+standardResources.notStarted = A\u00FAn no han sido arrancados los Recursos
+standardResources.null = El Documento base no puede ser nulo
+standardResources.slash = El Documento base {0} no debe de terminar con una barra
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_fr.properties
new file mode 100644
index 0000000..5180f65
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_fr.properties
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+fileResources.base=Le document base {0} n''existe pas ou n''est pas un r\u00e9pertoire lisible
+warResources.notWar=Doc base doit point\u00e9 vers un fichier WAR
+warResources.invalidWar=Fichier WAR invalide ou illisible  : {0}
+jarResources.syntax=Le document base {0} doit commencer par ''jar:'' et finir avec ''!/''
+resources.alreadyStarted=Les Ressources ont d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9es
+resources.connect=Impossible de se connecter au document base {0}
+resources.input=Impossible de cr\u00e9er l''input stream pour la ressource {0}
+resources.notStarted=Les ressources n''ont pas encore \u00e9t\u00e9 d\u00e9marr\u00e9es
+resources.null=Le document base ne peut \u00eatre nul
+resources.notFound=La ressource {0} est introuvable
+resources.path=Le chemin relatif de context {0} doit commencer par ''/''
+resources.alreadyBound=Le nom {0} est d\u00e9j\u00e0 r\u00e9f\u00e9renc\u00e9 par ce contexte
+resources.bindFailed=Le liage a \u00e9chou\u00e9: {0}
+resources.unbindFailed=Le d\u00e9liage a \u00e9chou\u00e9: {0}
+standardResources.alreadyStarted=Les ressources ont d\u00e9ja \u00e9t\u00e9 d\u00e9marr\u00e9es
+standardResources.directory=Le file base {0} n''est pas un r\u00e9pertoire
+standardResources.exists=Le file base {0} n''existe pas
+standardResources.notStarted=Les ressources n''ont pas encore \u00e9t\u00e9 d\u00e9marr\u00e9es
+standardResources.null=Le document base ne peut \u00eatre nul
+standardResources.slash=Le document base {0} ne doit pas se terminer par un ''/''
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_ja.properties
new file mode 100644
index 0000000..166cf5d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/LocalStrings_ja.properties
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+fileResources.base=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9 {0} \u304c\u5b58\u5728\u3057\u306a\u3044\u3001\u53c8\u306f\u8aad\u3081\u306a\u3044\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3067\u3059
+fileResources.listingNull={0} \u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u30ea\u30b9\u30c8\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093
+warResources.notWar=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u306fWAR\u30d5\u30a1\u30a4\u30eb\u3092\u793a\u3055\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+warResources.invalidWar=\u7121\u52b9\u53c8\u306f\u8aad\u3081\u306a\u3044WAR\u30d5\u30a1\u30a4\u30eb\u3067\u3059 : {0}
+jarResources.syntax=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9 {0} \u306f''jar:''\u3067\u59cb\u307e\u308a\u3001''!/''\u3067\u7d42\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+resources.alreadyStarted=\u30ea\u30bd\u30fc\u30b9\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+resources.connect=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9 {0} \u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093
+resources.input=\u30ea\u30bd\u30fc\u30b9 {0} \u306b\u5165\u529b\u30b9\u30c8\u30ea\u30fc\u30e0\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093
+resources.notStarted=\u30ea\u30bd\u30fc\u30b9\u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+resources.null=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u306fnull\u3067\u3042\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+resources.notFound=\u30ea\u30bd\u30fc\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+resources.path=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u76f8\u5bfe\u30d1\u30b9 {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+resources.alreadyBound=\u540d\u524d {0} \u306f\u65e2\u306b\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306b\u30d0\u30a4\u30f3\u30c9\u3055\u308c\u3066\u3044\u307e\u3059
+resources.bindFailed=\u30d0\u30a4\u30f3\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+resources.unbindFailed=\u30a2\u30f3\u30d0\u30a4\u30f3\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+standardResources.alreadyStarted=\u30ea\u30bd\u30fc\u30b9\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+standardResources.directory=\u30d5\u30a1\u30a4\u30eb\u30d9\u30fc\u30b9 {0} \u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+standardResources.exists=\u30d5\u30a1\u30a4\u30eb\u30d9\u30fc\u30b9 {0} \u306f\u5b58\u5728\u3057\u307e\u305b\u3093
+standardResources.notStarted=\u30ea\u30bd\u30fc\u30b9\u304c\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+standardResources.null=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u306fnull\u3067\u3042\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+standardResources.slash=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9 {0} \u306f\u30b9\u30e9\u30c3\u30b7\u30e5\u3067\u7d42\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/ProxyDirContext.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ProxyDirContext.java
new file mode 100644
index 0000000..de7c956
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ProxyDirContext.java
@@ -0,0 +1,1711 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.resources;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Hashtable;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.apache.naming.StringManager;
+
+/**
+ * Proxy Directory Context implementation.
+ *
+ * @author Remy Maucherat
+ * @version $Id: ProxyDirContext.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+
+public class ProxyDirContext implements DirContext {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final String CONTEXT = "context";
+    public static final String HOST = "host";
+
+
+    /**
+     * Immutable name not found exception.
+     */
+    protected static final NameNotFoundException NOT_FOUND_EXCEPTION =
+        new ImmutableNameNotFoundException();
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Builds a proxy directory context using the given environment.
+     */
+    public ProxyDirContext(Hashtable<String,String> env,
+            DirContext dirContext) {
+        this.env = env;
+        this.dirContext = dirContext;
+        if (dirContext instanceof BaseDirContext) {
+            // Initialize parameters based on the associated dir context, like
+            // the caching policy.
+            BaseDirContext baseDirContext = (BaseDirContext) dirContext;
+            if (baseDirContext.isCached()) {
+                try {
+                    cache = (ResourceCache) 
+                        Class.forName(cacheClassName).newInstance();
+                } catch (Exception e) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "resources.invalidCache", cacheClassName), e);
+                }
+                cache.setCacheMaxSize(baseDirContext.getCacheMaxSize());
+                cacheTTL = baseDirContext.getCacheTTL();
+                cacheObjectMaxSize = baseDirContext.getCacheObjectMaxSize();
+                // cacheObjectMaxSize must be less than cacheMaxSize
+                // Set a sensible limit
+                if (cacheObjectMaxSize > baseDirContext.getCacheMaxSize()/20) {
+                    cacheObjectMaxSize = baseDirContext.getCacheMaxSize()/20;
+                }
+            }
+        }
+        hostName = env.get(HOST);
+        contextName = env.get(CONTEXT);
+        int i = contextName.indexOf('#');
+        if (i == -1) {
+            contextPath = contextName;
+        } else {
+            contextPath = contextName.substring(0, i);
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Proxy DirContext (either this or the real proxy).
+     */
+    protected ProxyDirContext proxy = this;
+
+
+    /**
+     * Environment.
+     */
+    protected Hashtable<String,String> env;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Associated DirContext.
+     */
+    protected DirContext dirContext;
+
+
+    /**
+     * Virtual path.
+     */
+    protected String vPath = null;
+
+
+    /**
+     * Host name.
+     */
+    protected String hostName;
+
+
+    /**
+     * Context name.
+     */
+    protected String contextName;
+
+
+    /**
+     * Context path.
+     */
+    protected String contextPath;
+
+
+    /**
+     * Cache class.
+     */
+    protected String cacheClassName = 
+        "org.apache.naming.resources.ResourceCache";
+
+
+    /**
+     * Cache.
+     */
+    protected ResourceCache cache = null;
+
+
+    /**
+     * Cache TTL.
+     */
+    protected int cacheTTL = 5000; // 5s
+
+
+    /**
+     * Max size of resources which will have their content cached.
+     */
+    protected int cacheObjectMaxSize = 512; // 512 KB
+
+
+    /**
+     * Non cacheable resources.
+     */
+    protected String[] nonCacheable = { "/WEB-INF/lib/", "/WEB-INF/classes/" };
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Get the cache used for this context.
+     */
+    public ResourceCache getCache() {
+        return cache;
+    }
+
+
+    /**
+     * Return the actual directory context we are wrapping.
+     */
+    public DirContext getDirContext() {
+        return this.dirContext;
+    }
+
+
+    /**
+     * Return the document root for this component.
+     */
+    public String getDocBase() {
+        if (dirContext instanceof BaseDirContext)
+            return ((BaseDirContext) dirContext).getDocBase();
+        else
+            return "";
+    }
+
+
+    /**
+     * Return the host name.
+     */
+    public String getHostName() {
+        return this.hostName;
+    }
+
+
+    /**
+     * Return the context name.
+     */
+    public String getContextName() {
+        return this.contextName;
+    }
+
+
+    /**
+     * Return the context path.
+     */
+    public String getContextPath() {
+        return this.contextPath;
+    }
+
+
+    // -------------------------------------------------------- Context Methods
+
+
+    /**
+     * Retrieves the named object. If name is empty, returns a new instance 
+     * of this context (which represents the same naming context as this 
+     * context, but its environment may be modified independently and it may 
+     * be accessed concurrently).
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object lookup(Name name)
+        throws NamingException {
+        CacheEntry entry = cacheLookup(name.toString());
+        if (entry != null) {
+            if (!entry.exists) {
+                throw NOT_FOUND_EXCEPTION;
+            }
+            if (entry.resource != null) {
+                // Check content caching.
+                return entry.resource;
+            } else {
+                return entry.context;
+            }
+        }
+        Object object = dirContext.lookup(parseName(name));
+        if (object instanceof InputStream)
+            return new Resource((InputStream) object);
+        else
+            return object;
+    }
+
+
+    /**
+     * Retrieves the named object.
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object lookup(String name)
+        throws NamingException {
+        CacheEntry entry = cacheLookup(name);
+        if (entry != null) {
+            if (!entry.exists) {
+                throw NOT_FOUND_EXCEPTION;
+            }
+            if (entry.resource != null) {
+                return entry.resource;
+            } else {
+                return entry.context;
+            }
+        }
+        Object object = dirContext.lookup(parseName(name));
+        if (object instanceof InputStream) {
+            return new Resource((InputStream) object);
+        } else if (object instanceof DirContext) {
+            return object;
+        } else if (object instanceof Resource) {
+            return object;
+        } else {
+            return new Resource(new ByteArrayInputStream
+                (object.toString().getBytes()));
+        }
+    }
+
+
+    /**
+     * Binds a name to an object. All intermediate contexts and the target 
+     * context (that named by all but terminal atomic component of the name) 
+     * must already exist.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(Name name, Object obj)
+        throws NamingException {
+        dirContext.bind(parseName(name), obj);
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Binds a name to an object.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(String name, Object obj)
+        throws NamingException {
+        dirContext.bind(parseName(name), obj);
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Binds a name to an object, overwriting any existing binding. All 
+     * intermediate contexts and the target context (that named by all but 
+     * terminal atomic component of the name) must already exist.
+     * <p>
+     * If the object is a DirContext, any existing attributes associated with 
+     * the name are replaced with those of the object. Otherwise, any 
+     * existing attributes associated with the name remain unchanged.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(Name name, Object obj)
+        throws NamingException {
+        dirContext.rebind(parseName(name), obj);
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Binds a name to an object, overwriting any existing binding.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @exception javax.naming.directory.InvalidAttributesException if object
+     * did not supply all mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(String name, Object obj)
+        throws NamingException {
+        dirContext.rebind(parseName(name), obj);
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Unbinds the named object. Removes the terminal atomic name in name 
+     * from the target context--that named by all but the terminal atomic 
+     * part of name.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic 
+     * name is not bound in the target context, but throws 
+     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * 
+     * @param name the name to bind; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void unbind(Name name)
+        throws NamingException {
+        dirContext.unbind(parseName(name));
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Unbinds the named object.
+     * 
+     * @param name the name to bind; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void unbind(String name)
+        throws NamingException {
+        dirContext.unbind(parseName(name));
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Binds a new name to the object bound to an old name, and unbinds the 
+     * old name. Both names are relative to this context. Any attributes 
+     * associated with the old name become associated with the new name. 
+     * Intermediate contexts of the old name are not changed.
+     * 
+     * @param oldName the name of the existing binding; may not be empty
+     * @param newName the name of the new binding; may not be empty
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rename(Name oldName, Name newName)
+        throws NamingException {
+        dirContext.rename(parseName(oldName), parseName(newName));
+        cacheUnload(oldName.toString());
+    }
+
+
+    /**
+     * Binds a new name to the object bound to an old name, and unbinds the 
+     * old name.
+     * 
+     * @param oldName the name of the existing binding; may not be empty
+     * @param newName the name of the new binding; may not be empty
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rename(String oldName, String newName)
+        throws NamingException {
+        dirContext.rename(parseName(oldName), parseName(newName));
+        cacheUnload(oldName);
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the class 
+     * names of objects bound to them. The contents of any subcontexts are 
+     * not included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on 
+     * an enumeration previously returned is undefined.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the names and class names of the bindings in 
+     * this context. Each element of the enumeration is of type NameClassPair.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<NameClassPair> list(Name name)
+        throws NamingException {
+        return dirContext.list(parseName(name));
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the class 
+     * names of objects bound to them.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the names and class names of the bindings in 
+     * this context. Each element of the enumeration is of type NameClassPair.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<NameClassPair> list(String name)
+        throws NamingException {
+        return dirContext.list(parseName(name));
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the 
+     * objects bound to them. The contents of any subcontexts are not 
+     * included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on 
+     * an enumeration previously returned is undefined.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the bindings in this context. 
+     * Each element of the enumeration is of type Binding.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<Binding> listBindings(Name name)
+        throws NamingException {
+        return dirContext.listBindings(parseName(name));
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the 
+     * objects bound to them.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the bindings in this context. 
+     * Each element of the enumeration is of type Binding.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<Binding> listBindings(String name)
+        throws NamingException {
+        return dirContext.listBindings(parseName(name));
+    }
+
+
+    /**
+     * Destroys the named context and removes it from the namespace. Any 
+     * attributes associated with the name are also removed. Intermediate 
+     * contexts are not destroyed.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic 
+     * name is not bound in the target context, but throws 
+     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * 
+     * In a federated naming system, a context from one naming system may be 
+     * bound to a name in another. One can subsequently look up and perform 
+     * operations on the foreign context using a composite name. However, an 
+     * attempt destroy the context using this composite name will fail with 
+     * NotContextException, because the foreign context is not a "subcontext" 
+     * of the context in which it is bound. Instead, use unbind() to remove 
+     * the binding of the foreign context. Destroying the foreign context 
+     * requires that the destroySubcontext() be performed on a context from 
+     * the foreign context's "native" naming system.
+     * 
+     * @param name the name of the context to be destroyed; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception javax.naming.NotContextException if the name is bound but does
+     * not name a context, or does not name a context of the appropriate type
+     */
+    @Override
+    public void destroySubcontext(Name name)
+        throws NamingException {
+        dirContext.destroySubcontext(parseName(name));
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Destroys the named context and removes it from the namespace.
+     * 
+     * @param name the name of the context to be destroyed; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception javax.naming.NotContextException if the name is bound but does
+     * not name a context, or does not name a context of the appropriate type
+     */
+    @Override
+    public void destroySubcontext(String name)
+        throws NamingException {
+        dirContext.destroySubcontext(parseName(name));
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Creates and binds a new context. Creates a new context with the given 
+     * name and binds it in the target context (that named by all but 
+     * terminal atomic component of the name). All intermediate contexts and 
+     * the target context must already exist.
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if creation
+     * of the sub-context requires specification of mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Context createSubcontext(Name name)
+        throws NamingException {
+        Context context = dirContext.createSubcontext(parseName(name));
+        cacheUnload(name.toString());
+        return context;
+    }
+
+
+    /**
+     * Creates and binds a new context.
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if creation
+     * of the sub-context requires specification of mandatory attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Context createSubcontext(String name)
+        throws NamingException {
+        Context context = dirContext.createSubcontext(parseName(name));
+        cacheUnload(name);
+        return context;
+    }
+
+
+    /**
+     * Retrieves the named object, following links except for the terminal 
+     * atomic component of the name. If the object bound to name is not a 
+     * link, returns the object itself.
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name, not following the terminal link 
+     * (if any).
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object lookupLink(Name name)
+        throws NamingException {
+        return dirContext.lookupLink(parseName(name));
+    }
+
+
+    /**
+     * Retrieves the named object, following links except for the terminal 
+     * atomic component of the name.
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name, not following the terminal link 
+     * (if any).
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object lookupLink(String name)
+        throws NamingException {
+        return dirContext.lookupLink(parseName(name));
+    }
+
+
+    /**
+     * Retrieves the parser associated with the named context. In a 
+     * federation of namespaces, different naming systems will parse names 
+     * differently. This method allows an application to get a parser for 
+     * parsing names into their atomic components using the naming convention 
+     * of a particular naming system. Within any single naming system, 
+     * NameParser objects returned by this method must be equal (using the 
+     * equals() test).
+     * 
+     * @param name the name of the context from which to get the parser
+     * @return a name parser that can parse compound names into their atomic 
+     * components
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NameParser getNameParser(Name name)
+        throws NamingException {
+        return dirContext.getNameParser(parseName(name));
+    }
+
+
+    /**
+     * Retrieves the parser associated with the named context.
+     * 
+     * @param name the name of the context from which to get the parser
+     * @return a name parser that can parse compound names into their atomic 
+     * components
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NameParser getNameParser(String name)
+        throws NamingException {
+        return dirContext.getNameParser(parseName(name));
+    }
+
+
+    /**
+     * Composes the name of this context with a name relative to this context.
+     * <p>
+     * Given a name (name) relative to this context, and the name (prefix) 
+     * of this context relative to one of its ancestors, this method returns 
+     * the composition of the two names using the syntax appropriate for the 
+     * naming system(s) involved. That is, if name names an object relative 
+     * to this context, the result is the name of the same object, but 
+     * relative to the ancestor context. None of the names may be null.
+     * 
+     * @param name a name relative to this context
+     * @param prefix the name of this context relative to one of its ancestors
+     * @return the composition of prefix and name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Name composeName(Name name, Name prefix)
+        throws NamingException {
+        Name prefixClone = (Name) prefix.clone();
+        return prefixClone.addAll(name);
+    }
+
+
+    /**
+     * Composes the name of this context with a name relative to this context.
+     * 
+     * @param name a name relative to this context
+     * @param prefix the name of this context relative to one of its ancestors
+     * @return the composition of prefix and name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public String composeName(String name, String prefix)
+        throws NamingException {
+        return prefix + "/" + name;
+    }
+
+
+    /**
+     * Adds a new environment property to the environment of this context. If 
+     * the property already exists, its value is overwritten.
+     * 
+     * @param propName the name of the environment property to add; may not 
+     * be null
+     * @param propVal the value of the property to add; may not be null
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object addToEnvironment(String propName, Object propVal)
+        throws NamingException {
+        return dirContext.addToEnvironment(propName, propVal);
+    }
+
+
+    /**
+     * Removes an environment property from the environment of this context. 
+     * 
+     * @param propName the name of the environment property to remove; 
+     * may not be null
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object removeFromEnvironment(String propName)
+        throws NamingException {
+        return dirContext.removeFromEnvironment(propName);
+    }
+
+
+    /**
+     * Retrieves the environment in effect for this context. See class 
+     * description for more details on environment properties. 
+     * The caller should not make any changes to the object returned: their 
+     * effect on the context is undefined. The environment of this context 
+     * may be changed using addToEnvironment() and removeFromEnvironment().
+     * 
+     * @return the environment of this context; never null
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Hashtable<?,?> getEnvironment()
+        throws NamingException {
+        return dirContext.getEnvironment();
+    }
+
+
+    /**
+     * Closes this context. This method releases this context's resources 
+     * immediately, instead of waiting for them to be released automatically 
+     * by the garbage collector.
+     * This method is idempotent: invoking it on a context that has already 
+     * been closed has no effect. Invoking any other method on a closed 
+     * context is not allowed, and results in undefined behaviour.
+     * 
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void close()
+        throws NamingException {
+        dirContext.close();
+    }
+
+
+    /**
+     * Retrieves the full name of this context within its own namespace.
+     * <p>
+     * Many naming services have a notion of a "full name" for objects in 
+     * their respective namespaces. For example, an LDAP entry has a 
+     * distinguished name, and a DNS record has a fully qualified name. This 
+     * method allows the client application to retrieve this name. The string 
+     * returned by this method is not a JNDI composite name and should not be 
+     * passed directly to context methods. In naming systems for which the 
+     * notion of full name does not make sense, 
+     * OperationNotSupportedException is thrown.
+     * 
+     * @return this context's name in its own namespace; never null
+     * @exception javax.naming.OperationNotSupportedException if the naming
+     * system does not have the notion of a full name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public String getNameInNamespace()
+        throws NamingException {
+        return dirContext.getNameInNamespace();
+    }
+
+
+    // ----------------------------------------------------- DirContext Methods
+
+
+    /**
+     * Retrieves all of the attributes associated with a named object. 
+     * 
+     * @return the set of attributes associated with name. 
+     * Returns an empty attribute set if name has no attributes; never null.
+     * @param name the name of the object from which to retrieve attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Attributes getAttributes(Name name)
+        throws NamingException {
+        CacheEntry entry = cacheLookup(name.toString());
+        if (entry != null) {
+            if (!entry.exists) {
+                throw NOT_FOUND_EXCEPTION;
+            }
+            return entry.attributes;
+        }
+        Attributes attributes = dirContext.getAttributes(parseName(name));
+        if (!(attributes instanceof ResourceAttributes)) {
+            attributes = new ResourceAttributes(attributes);
+        }
+        return attributes;
+    }
+
+
+    /**
+     * Retrieves all of the attributes associated with a named object.
+     * 
+     * @return the set of attributes associated with name
+     * @param name the name of the object from which to retrieve attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Attributes getAttributes(String name)
+        throws NamingException {
+        CacheEntry entry = cacheLookup(name);
+        if (entry != null) {
+            if (!entry.exists) {
+                throw NOT_FOUND_EXCEPTION;
+            }
+            return entry.attributes;
+        }
+        Attributes attributes = dirContext.getAttributes(parseName(name));
+        if (!(attributes instanceof ResourceAttributes)) {
+            attributes = new ResourceAttributes(attributes);
+        }
+        return attributes;
+    }
+
+
+    /**
+     * Retrieves selected attributes associated with a named object. 
+     * See the class description regarding attribute models, attribute type 
+     * names, and operational attributes.
+     * 
+     * @return the requested attributes; never null
+     * @param name the name of the object from which to retrieve attributes
+     * @param attrIds the identifiers of the attributes to retrieve. null 
+     * indicates that all attributes should be retrieved; an empty array 
+     * indicates that none should be retrieved
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Attributes getAttributes(Name name, String[] attrIds)
+        throws NamingException {
+        Attributes attributes = 
+            dirContext.getAttributes(parseName(name), attrIds);
+        if (!(attributes instanceof ResourceAttributes)) {
+            attributes = new ResourceAttributes(attributes);
+        }
+        return attributes;
+    }
+
+
+    /**
+     * Retrieves selected attributes associated with a named object.
+     * 
+     * @return the requested attributes; never null
+     * @param name the name of the object from which to retrieve attributes
+     * @param attrIds the identifiers of the attributes to retrieve. null 
+     * indicates that all attributes should be retrieved; an empty array 
+     * indicates that none should be retrieved
+     * @exception NamingException if a naming exception is encountered
+     */
+     @Override
+    public Attributes getAttributes(String name, String[] attrIds)
+         throws NamingException {
+        Attributes attributes = 
+            dirContext.getAttributes(parseName(name), attrIds);
+        if (!(attributes instanceof ResourceAttributes)) {
+            attributes = new ResourceAttributes(attributes);
+        }
+        return attributes;
+     }
+
+
+    /**
+     * Modifies the attributes associated with a named object. The order of 
+     * the modifications is not specified. Where possible, the modifications 
+     * are performed atomically.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mod_op the modification operation, one of: ADD_ATTRIBUTE, 
+     * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
+     * @param attrs the attributes to be used for the modification; may not 
+     * be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(Name name, int mod_op, Attributes attrs)
+        throws NamingException {
+        dirContext.modifyAttributes(parseName(name), mod_op, attrs);
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mod_op the modification operation, one of: ADD_ATTRIBUTE, 
+     * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
+     * @param attrs the attributes to be used for the modification; may not 
+     * be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(String name, int mod_op, Attributes attrs)
+        throws NamingException {
+        dirContext.modifyAttributes(parseName(name), mod_op, attrs);
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object using an an 
+     * ordered list of modifications. The modifications are performed in the 
+     * order specified. Each modification specifies a modification operation 
+     * code and an attribute on which to operate. Where possible, the 
+     * modifications are performed atomically.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mods an ordered sequence of modifications to be performed; may 
+     * not be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(Name name, ModificationItem[] mods)
+        throws NamingException {
+        dirContext.modifyAttributes(parseName(name), mods);
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object using an an 
+     * ordered list of modifications.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mods an ordered sequence of modifications to be performed; may 
+     * not be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(String name, ModificationItem[] mods)
+        throws NamingException {
+        dirContext.modifyAttributes(parseName(name), mods);
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes. If attrs 
+     * is null, the resulting binding will have the attributes associated 
+     * with obj if obj is a DirContext, and no attributes otherwise. If attrs 
+     * is non-null, the resulting binding will have attrs as its attributes; 
+     * any attributes associated with obj are ignored.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(Name name, Object obj, Attributes attrs)
+        throws NamingException {
+        dirContext.bind(parseName(name), obj, attrs);
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(String name, Object obj, Attributes attrs)
+        throws NamingException {
+        dirContext.bind(parseName(name), obj, attrs);
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes, 
+     * overwriting any existing binding. If attrs is null and obj is a 
+     * DirContext, the attributes from obj are used. If attrs is null and obj 
+     * is not a DirContext, any existing attributes associated with the object
+     * already bound in the directory remain unchanged. If attrs is non-null, 
+     * any existing attributes associated with the object already bound in 
+     * the directory are removed and attrs is associated with the named 
+     * object. If obj is a DirContext and attrs is non-null, the attributes 
+     * of obj are ignored.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(Name name, Object obj, Attributes attrs)
+        throws NamingException {
+        dirContext.rebind(parseName(name), obj, attrs);
+        cacheUnload(name.toString());
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes, 
+     * overwriting any existing binding.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(String name, Object obj, Attributes attrs)
+        throws NamingException {
+        dirContext.rebind(parseName(name), obj, attrs);
+        cacheUnload(name);
+    }
+
+
+    /**
+     * Creates and binds a new context, along with associated attributes. 
+     * This method creates a new subcontext with the given name, binds it in 
+     * the target context (that named by all but terminal atomic component of 
+     * the name), and associates the supplied attributes with the newly 
+     * created object. All intermediate and target contexts must already 
+     * exist. If attrs is null, this method is equivalent to 
+     * Context.createSubcontext().
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @param attrs the attributes to associate with the newly created context
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if attrs
+     * does not contain all the mandatory attributes required for creation
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext createSubcontext(Name name, Attributes attrs)
+        throws NamingException {
+        DirContext context = 
+            dirContext.createSubcontext(parseName(name), attrs);
+        cacheUnload(name.toString());
+        return context;
+    }
+
+
+    /**
+     * Creates and binds a new context, along with associated attributes.
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @param attrs the attributes to associate with the newly created context
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if attrs
+     * does not contain all the mandatory attributes required for creation
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext createSubcontext(String name, Attributes attrs)
+        throws NamingException {
+        DirContext context = 
+            dirContext.createSubcontext(parseName(name), attrs);
+        cacheUnload(name);
+        return context;
+    }
+
+
+    /**
+     * Retrieves the schema associated with the named object. The schema 
+     * describes rules regarding the structure of the namespace and the 
+     * attributes stored within it. The schema specifies what types of 
+     * objects can be added to the directory and where they can be added; 
+     * what mandatory and optional attributes an object can have. The range 
+     * of support for schemas is directory-specific.
+     * 
+     * @param name the name of the object whose schema is to be retrieved
+     * @return the schema associated with the context; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchema(Name name)
+        throws NamingException {
+        return dirContext.getSchema(parseName(name));
+    }
+
+
+    /**
+     * Retrieves the schema associated with the named object.
+     * 
+     * @param name the name of the object whose schema is to be retrieved
+     * @return the schema associated with the context; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchema(String name)
+        throws NamingException {
+        return dirContext.getSchema(parseName(name));
+    }
+
+
+    /**
+     * Retrieves a context containing the schema objects of the named 
+     * object's class definitions.
+     * 
+     * @param name the name of the object whose object class definition is to 
+     * be retrieved
+     * @return the DirContext containing the named object's class 
+     * definitions; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchemaClassDefinition(Name name)
+        throws NamingException {
+        return dirContext.getSchemaClassDefinition(parseName(name));
+    }
+
+
+    /**
+     * Retrieves a context containing the schema objects of the named 
+     * object's class definitions.
+     * 
+     * @param name the name of the object whose object class definition is to 
+     * be retrieved
+     * @return the DirContext containing the named object's class 
+     * definitions; never null
+     * @exception javax.naming.OperationNotSupportedException if schema not
+     * supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchemaClassDefinition(String name)
+        throws NamingException {
+        return dirContext.getSchemaClassDefinition(parseName(name));
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes, and retrieves selected attributes. The search is 
+     * performed using the default SearchControls settings.
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @param attributesToReturn the attributes to return. null indicates 
+     * that all attributes are to be returned; an empty array indicates that 
+     * none are to be returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(Name name,
+            Attributes matchingAttributes, String[] attributesToReturn)
+        throws NamingException {
+        return dirContext.search(parseName(name), matchingAttributes, 
+                                 attributesToReturn);
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes, and retrieves selected attributes.
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @param attributesToReturn the attributes to return. null indicates 
+     * that all attributes are to be returned; an empty array indicates that 
+     * none are to be returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            Attributes matchingAttributes, String[] attributesToReturn)
+        throws NamingException {
+        return dirContext.search(parseName(name), matchingAttributes, 
+                                 attributesToReturn);
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes. This method returns all the attributes of such objects. 
+     * It is equivalent to supplying null as the atributesToReturn parameter 
+     * to the method search(Name, Attributes, String[]).
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(Name name,
+            Attributes matchingAttributes) throws NamingException {
+        return dirContext.search(parseName(name), matchingAttributes);
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes.
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            Attributes matchingAttributes) throws NamingException {
+        return dirContext.search(parseName(name), matchingAttributes);
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filter the filter expression to use for the search; may not be 
+     * null
+     * @param cons the search controls that control the search. If null, 
+     * the default search controls are used (equivalent to 
+     * (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy 
+     * the filter; never null
+     * @exception javax.naming.directory.InvalidSearchFilterException if the
+     * search filter specified is not supported or understood by the underlying
+     * directory
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(Name name, String filter, 
+                                    SearchControls cons)
+        throws NamingException {
+        return dirContext.search(parseName(name), filter, cons);
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filter the filter expression to use for the search; may not be 
+     * null
+     * @param cons the search controls that control the search. If null, 
+     * the default search controls are used (equivalent to 
+     * (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy 
+     * the filter; never null
+     * @exception javax.naming.directory.InvalidSearchFilterException if the
+     * search filter specified is not supported or understood by the underlying
+     * directory
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name, String filter, 
+                                    SearchControls cons)
+        throws NamingException {
+        return dirContext.search(parseName(name), filter, cons);
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filterExpr the filter expression to use for the search. 
+     * The expression may contain variables of the form "{i}" where i is a 
+     * nonnegative integer. May not be null.
+     * @param filterArgs the array of arguments to substitute for the 
+     * variables in filterExpr. The value of filterArgs[i] will replace each 
+     * occurrence of "{i}". If null, equivalent to an empty array.
+     * @param cons the search controls that control the search. If null, the 
+     * default search controls are used (equivalent to (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisy the 
+     * filter; never null
+     * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i} 
+     * expressions where i is outside the bounds of the array filterArgs
+     * @exception javax.naming.directory.InvalidSearchControlsException if cons
+     * contains invalid settings
+     * @exception javax.naming.directory.InvalidSearchFilterException if
+     * filterExpr with filterArgs represents an invalid search filter
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(Name name, String filterExpr,
+                                    Object[] filterArgs, SearchControls cons)
+        throws NamingException {
+        return dirContext.search(parseName(name), filterExpr, filterArgs, 
+                                 cons);
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filterExpr the filter expression to use for the search. 
+     * The expression may contain variables of the form "{i}" where i is a 
+     * nonnegative integer. May not be null.
+     * @param filterArgs the array of arguments to substitute for the 
+     * variables in filterExpr. The value of filterArgs[i] will replace each 
+     * occurrence of "{i}". If null, equivalent to an empty array.
+     * @param cons the search controls that control the search. If null, the 
+     * default search controls are used (equivalent to (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisy the 
+     * filter; never null
+     * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i} 
+     * expressions where i is outside the bounds of the array filterArgs
+     * @exception javax.naming.directory.InvalidSearchControlsException if cons
+     * contains invalid settings
+     * @exception javax.naming.directory.InvalidSearchFilterException if
+     * filterExpr with filterArgs represents an invalid search filter
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            String filterExpr, Object[] filterArgs, SearchControls cons)
+        throws NamingException {
+        return dirContext.search(parseName(name), filterExpr, filterArgs, 
+                                 cons);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Retrieves the named object as a cache entry, without any exception.
+     * 
+     * @param name the name of the object to look up
+     * @return the cache entry bound to name
+     */
+    public CacheEntry lookupCache(String name) {
+        CacheEntry entry = cacheLookup(name);
+        if (entry == null) {
+            entry = new CacheEntry();
+            entry.name = name;
+            try {
+                Object object = dirContext.lookup(parseName(name));
+                if (object instanceof InputStream) {
+                    entry.resource = new Resource((InputStream) object);
+                } else if (object instanceof DirContext) {
+                    entry.context = (DirContext) object;
+                } else if (object instanceof Resource) {
+                    entry.resource = (Resource) object;
+                } else {
+                    entry.resource = new Resource(new ByteArrayInputStream
+                        (object.toString().getBytes()));
+                }
+                Attributes attributes = dirContext.getAttributes(parseName(name));
+                if (!(attributes instanceof ResourceAttributes)) {
+                    attributes = new ResourceAttributes(attributes);
+                }
+                entry.attributes = (ResourceAttributes) attributes;
+            } catch (NamingException e) {
+                entry.exists = false;
+            }
+        }
+        return entry;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Parses a name.
+     * 
+     * @return the parsed name
+     * @throws NamingException if the name cannot be parsed
+     */
+    protected String parseName(String name) throws NamingException {
+        return name;
+    }
+
+
+    /**
+     * Parses a name.
+     * 
+     * @return the parsed name
+     * @throws NamingException if the name cannot be parsed
+     */
+    protected Name parseName(Name name) throws NamingException {
+        return name;
+    }
+
+
+    /**
+     * Lookup in cache.
+     */
+    protected CacheEntry cacheLookup(String lookupName) {
+        if (cache == null)
+            return (null);
+        String name;
+        if (lookupName == null) {
+            name = "";
+        } else {
+            name = lookupName;
+        }
+        for (int i = 0; i < nonCacheable.length; i++) {
+            if (name.startsWith(nonCacheable[i])) {
+                return (null);
+            }
+        }
+        CacheEntry cacheEntry = cache.lookup(name);
+        if (cacheEntry == null) {
+            cacheEntry = new CacheEntry();
+            cacheEntry.name = name;
+            // Load entry
+            cacheLoad(cacheEntry);
+        } else {
+            if (!validate(cacheEntry)) {
+                if (!revalidate(cacheEntry)) {
+                    cacheUnload(cacheEntry.name);
+                    return (null);
+                } else {
+                    cacheEntry.timestamp = 
+                        System.currentTimeMillis() + cacheTTL;
+                }
+            }
+            cacheEntry.accessCount++;
+        }
+        return (cacheEntry);
+    }
+
+
+    /**
+     * Validate entry.
+     */
+    protected boolean validate(CacheEntry entry) {
+        if (((!entry.exists)
+             || (entry.context != null)
+             || ((entry.resource != null) 
+                 && (entry.resource.getContent() != null)))
+            && (System.currentTimeMillis() < entry.timestamp)) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Revalidate entry.
+     */
+    protected boolean revalidate(CacheEntry entry) {
+        // Get the attributes at the given path, and check the last 
+        // modification date
+        if (!entry.exists)
+            return false;
+        if (entry.attributes == null)
+            return false;
+        long lastModified = entry.attributes.getLastModified();
+        long contentLength = entry.attributes.getContentLength();
+        if (lastModified <= 0)
+            return false;
+        try {
+            Attributes tempAttributes = dirContext.getAttributes(entry.name);
+            ResourceAttributes attributes = null;
+            if (!(tempAttributes instanceof ResourceAttributes)) {
+                attributes = new ResourceAttributes(tempAttributes);
+            } else {
+                attributes = (ResourceAttributes) tempAttributes;
+            }
+            long lastModified2 = attributes.getLastModified();
+            long contentLength2 = attributes.getContentLength();
+            return (lastModified == lastModified2) 
+                && (contentLength == contentLength2);
+        } catch (NamingException e) {
+            return false;
+        }
+    }
+
+
+    /**
+     * Load entry into cache.
+     */
+    protected void cacheLoad(CacheEntry entry) {
+
+        String name = entry.name;
+
+        // Retrieve missing info
+        boolean exists = true;
+
+        // Retrieving attributes
+        if (entry.attributes == null) {
+            try {
+                Attributes attributes = dirContext.getAttributes(entry.name);
+                if (!(attributes instanceof ResourceAttributes)) {
+                    entry.attributes = 
+                        new ResourceAttributes(attributes);
+                } else {
+                    entry.attributes = (ResourceAttributes) attributes;
+                }
+            } catch (NamingException e) {
+                exists = false;
+            }
+        }
+
+        // Retrieving object
+        if ((exists) && (entry.resource == null) && (entry.context == null)) {
+            try {
+                Object object = dirContext.lookup(name);
+                if (object instanceof InputStream) {
+                    entry.resource = new Resource((InputStream) object);
+                } else if (object instanceof DirContext) {
+                    entry.context = (DirContext) object;
+                } else if (object instanceof Resource) {
+                    entry.resource = (Resource) object;
+                } else {
+                    entry.resource = new Resource(new ByteArrayInputStream
+                        (object.toString().getBytes()));
+                }
+            } catch (NamingException e) {
+                exists = false;
+            }
+        }
+
+        // Load object content
+        if ((exists) && (entry.resource != null) 
+            && (entry.resource.getContent() == null) 
+            && (entry.attributes.getContentLength() >= 0)
+            && (entry.attributes.getContentLength() < 
+                (cacheObjectMaxSize * 1024))) {
+            int length = (int) entry.attributes.getContentLength();
+            // The entry size is 1 + the resource size in KB, if it will be 
+            // cached
+            entry.size += (entry.attributes.getContentLength() / 1024);
+            InputStream is = null;
+            try {
+                is = entry.resource.streamContent();
+                int pos = 0;
+                byte[] b = new byte[length];
+                while (pos < length) {
+                    int n = is.read(b, pos, length - pos);
+                    if (n < 0)
+                        break;
+                    pos = pos + n;
+                }
+                entry.resource.setContent(b);
+            } catch (IOException e) {
+                // Ignore
+            } finally {
+                try {
+                    if (is != null)
+                        is.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        // Set existence flag
+        entry.exists = exists;
+
+        // Set timestamp
+        entry.timestamp = System.currentTimeMillis() + cacheTTL;
+
+        // Add new entry to cache
+        synchronized (cache) {
+            // Check cache size, and remove elements if too big
+            if ((cache.lookup(name) == null) && cache.allocate(entry.size)) {
+                cache.load(entry);
+            }
+        }
+
+    }
+
+
+    /**
+     * Remove entry from cache.
+     */
+    protected boolean cacheUnload(String name) {
+        if (cache == null)
+            return false;
+        // To ensure correct operation, particularly of WebDAV, unload
+        // the resource with and without a trailing /
+        String name2;
+        if (name.endsWith("/")) {
+            name2 = name.substring(0, name.length() -1);
+        } else {
+            name2 = name + "/";
+        }
+        synchronized (cache) {
+            boolean result = cache.unload(name);
+            cache.unload(name2);
+            return result;
+        }
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/RecyclableNamingEnumeration.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/RecyclableNamingEnumeration.java
new file mode 100644
index 0000000..0c1c0be
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/RecyclableNamingEnumeration.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.resources;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+
+/**
+ * Naming enumeration implementation.
+ *
+ * @author Remy Maucherat
+ * @version $Id: RecyclableNamingEnumeration.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+
+public class RecyclableNamingEnumeration<E> 
+    implements NamingEnumeration<E> {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public RecyclableNamingEnumeration(Vector<E> entries) {
+        this.entries = entries;
+        recycle();
+    }
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * Entries.
+     */
+    protected Vector<E> entries;
+
+
+    /**
+     * Underlying enumeration.
+     */
+    protected Enumeration<E> enumeration;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Retrieves the next element in the enumeration.
+     */
+    @Override
+    public E next()
+        throws NamingException {
+        return nextElement();
+    }
+
+
+    /**
+     * Determines whether there are any more elements in the enumeration.
+     */
+    @Override
+    public boolean hasMore()
+        throws NamingException {
+        return enumeration.hasMoreElements();
+    }
+
+
+    /**
+     * Closes this enumeration.
+     */
+    @Override
+    public void close() throws NamingException {
+        // NO-OP
+    }
+
+
+    @Override
+    public boolean hasMoreElements() {
+        return enumeration.hasMoreElements();
+    }
+
+
+    @Override
+    public E nextElement() {
+        return enumeration.nextElement();
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Recycle.
+     */
+    void recycle() {
+        enumeration = entries.elements();
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/Resource.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/Resource.java
new file mode 100644
index 0000000..e8a99cc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/Resource.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Encapsulates the contents of a resource.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class Resource {
+    
+    
+    // ----------------------------------------------------------- Constructors
+    
+    
+    public Resource() {
+        // NO-OP
+    }
+    
+    
+    public Resource(InputStream inputStream) {
+        setContent(inputStream);
+    }
+    
+    
+    public Resource(byte[] binaryContent) {
+        setContent(binaryContent);
+    }
+    
+    
+    // ----------------------------------------------------- Instance Variables
+    
+    
+    /**
+     * Binary content.
+     */
+    protected byte[] binaryContent = null;
+    
+    
+    /**
+     * Input stream.
+     */
+    protected InputStream inputStream = null;
+    
+    
+    // ------------------------------------------------------------- Properties
+    
+    
+    /**
+     * Content accessor.
+     * 
+     * @return InputStream
+     * @throws IOException
+     */
+    public InputStream streamContent() throws IOException {
+        if (binaryContent != null) {
+            return new ByteArrayInputStream(binaryContent);
+        }
+        return inputStream;
+    }
+    
+    
+    /**
+     * Content accessor.
+     * 
+     * @return binary content
+     */
+    public byte[] getContent() {
+        return binaryContent;
+    }
+    
+    
+    /**
+     * Content mutator.
+     * 
+     * @param inputStream New input stream
+     */
+    public void setContent(InputStream inputStream) {
+        this.inputStream = inputStream;
+    }
+    
+    
+    /**
+     * Content mutator.
+     * 
+     * @param binaryContent New bin content
+     */
+    public void setContent(byte[] binaryContent) {
+        this.binaryContent = binaryContent;
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/ResourceAttributes.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ResourceAttributes.java
new file mode 100644
index 0000000..6271a3d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ResourceAttributes.java
@@ -0,0 +1,979 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+
+/**
+ * Attributes implementation.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class ResourceAttributes implements Attributes {
+    
+    
+    // -------------------------------------------------------------- Constants
+    
+    private static final long serialVersionUID = 1L;
+    
+    // Default attribute names
+    
+    /**
+     * Creation date.
+     */
+    public static final String CREATION_DATE = "creationdate";
+    
+    
+    /**
+     * Creation date.
+     */
+    public static final String ALTERNATE_CREATION_DATE = "creation-date";
+    
+    
+    /**
+     * Last modification date.
+     */
+    public static final String LAST_MODIFIED = "getlastmodified";
+    
+    
+    /**
+     * Last modification date.
+     */
+    public static final String ALTERNATE_LAST_MODIFIED = "last-modified";
+    
+    
+    /**
+     * Name.
+     */
+    public static final String NAME = "displayname";
+    
+    
+    /**
+     * Type.
+     */
+    public static final String TYPE = "resourcetype";
+    
+    
+    /**
+     * Type.
+     */
+    public static final String ALTERNATE_TYPE = "content-type";
+    
+    
+    /**
+     * MIME type of the content.
+     */
+    public static final String CONTENT_TYPE = "getcontenttype";
+    
+    
+    /**
+     * Content length.
+     */
+    public static final String CONTENT_LENGTH = "getcontentlength";
+    
+    
+    /**
+     * Content length.
+     */
+    public static final String ALTERNATE_CONTENT_LENGTH = "content-length";
+    
+    
+    /**
+     * ETag.
+     */
+    public static final String ETAG = "getetag";
+    
+    
+    /**
+     * ETag.
+     */
+    public static final String ALTERNATE_ETAG = "etag";
+    
+    
+    /**
+     * Collection type.
+     */
+    public static final String COLLECTION_TYPE = "<collection/>";
+    
+    
+    /**
+     * HTTP date format.
+     */
+    protected static final SimpleDateFormat format =
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+    
+    
+    /**
+     * Date formats using for Date parsing.
+     */
+    protected static final SimpleDateFormat formats[] = {
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+    };
+    
+    
+    protected static final TimeZone gmtZone = TimeZone.getTimeZone("GMT");
+
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    static {
+
+        format.setTimeZone(gmtZone);
+
+        formats[0].setTimeZone(gmtZone);
+        formats[1].setTimeZone(gmtZone);
+        formats[2].setTimeZone(gmtZone);
+
+    }
+
+
+    // ----------------------------------------------------------- Constructors
+    
+    
+    /**
+     * Default constructor.
+     */
+    public ResourceAttributes() {
+        // NO-OP
+    }
+    
+    
+    /**
+     * Merges with another attribute set.
+     */
+    public ResourceAttributes(Attributes attributes) {
+        this.attributes = attributes;
+    }
+    
+    
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Collection flag.
+     */
+    protected boolean collection = false;
+
+
+    /**
+     * Content length.
+     */
+    protected long contentLength = -1;
+
+
+    /**
+     * Creation time.
+     */
+    protected long creation = -1;
+
+
+    /**
+     * Creation date.
+     */
+    protected Date creationDate = null;
+
+
+    /**
+     * Last modified time.
+     */
+    protected long lastModified = -1;
+
+
+    /**
+     * Last modified date.
+     */
+    protected Date lastModifiedDate = null;
+
+    
+    /**
+     * Last modified date in HTTP format.
+     */
+    protected String lastModifiedHttp = null;
+    
+
+    /**
+     * MIME type.
+     */
+    protected String mimeType = null;
+    
+
+    /**
+     * Name.
+     */
+    protected String name = null;
+
+
+    /**
+     * Weak ETag.
+     */
+    protected String weakETag = null;
+
+
+    /**
+     * Strong ETag.
+     */
+    protected String strongETag = null;
+
+
+    /**
+     * External attributes.
+     */
+    protected Attributes attributes = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Is collection.
+     */
+    public boolean isCollection() {
+        if (attributes != null) {
+            return (COLLECTION_TYPE.equals(getResourceType()));
+        } else {
+            return (collection);
+        }
+    }
+    
+    
+    /**
+     * Set collection flag.
+     *
+     * @param collection New flag value
+     */
+    public void setCollection(boolean collection) {
+        this.collection = collection;
+        if (attributes != null) {
+            String value = "";
+            if (collection)
+                value = COLLECTION_TYPE;
+            attributes.put(TYPE, value);
+        }
+    }
+    
+    
+    /**
+     * Get content length.
+     * 
+     * @return content length value
+     */
+    public long getContentLength() {
+        if (contentLength != -1L)
+            return contentLength;
+        if (attributes != null) {
+            Attribute attribute = attributes.get(CONTENT_LENGTH);
+            if (attribute != null) {
+                try {
+                    Object value = attribute.get();
+                    if (value instanceof Long) {
+                        contentLength = ((Long) value).longValue();
+                    } else {
+                        try {
+                            contentLength = Long.parseLong(value.toString());
+                        } catch (NumberFormatException e) {
+                            // Ignore
+                        }
+                    }
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        return contentLength;
+    }
+    
+    
+    /**
+     * Set content length.
+     * 
+     * @param contentLength New content length value
+     */
+    public void setContentLength(long contentLength) {
+        this.contentLength = contentLength;
+        if (attributes != null)
+            attributes.put(CONTENT_LENGTH, Long.valueOf(contentLength));
+    }
+    
+    
+    /**
+     * Get creation time.
+     * 
+     * @return creation time value
+     */
+    public long getCreation() {
+        if (creation != -1L)
+            return creation;
+        if (creationDate != null)
+            return creationDate.getTime();
+        if (attributes != null) {
+            Attribute attribute = attributes.get(CREATION_DATE);
+            if (attribute != null) {
+                try {
+                    Object value = attribute.get();
+                    if (value instanceof Long) {
+                        creation = ((Long) value).longValue();
+                    } else if (value instanceof Date) {
+                        creation = ((Date) value).getTime();
+                        creationDate = (Date) value;
+                    } else {
+                        String creationDateValue = value.toString();
+                        Date result = null;
+                        // Parsing the HTTP Date
+                        for (int i = 0; (result == null) && 
+                                 (i < formats.length); i++) {
+                            try {
+                                result = formats[i].parse(creationDateValue);
+                            } catch (ParseException e) {
+                                // Ignore
+                            }
+                        }
+                        if (result != null) {
+                            creation = result.getTime();
+                            creationDate = result;
+                        }
+                    }
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        return creation;
+    }
+    
+    
+    /**
+     * Set creation.
+     * 
+     * @param creation New creation value
+     */
+    public void setCreation(long creation) {
+        this.creation = creation;
+        this.creationDate = null;
+        if (attributes != null)
+            attributes.put(CREATION_DATE, new Date(creation));
+    }
+    
+    
+    /**
+     * Get creation date.
+     * 
+     * @return Creation date value
+     */
+    public Date getCreationDate() {
+        if (creationDate != null)
+            return creationDate;
+        if (creation != -1L) {
+            creationDate = new Date(creation);
+            return creationDate;
+        }
+        if (attributes != null) {
+            Attribute attribute = attributes.get(CREATION_DATE);
+            if (attribute != null) {
+                try {
+                    Object value = attribute.get();
+                    if (value instanceof Long) {
+                        creation = ((Long) value).longValue();
+                        creationDate = new Date(creation);
+                    } else if (value instanceof Date) {
+                        creation = ((Date) value).getTime();
+                        creationDate = (Date) value;
+                    } else {
+                        String creationDateValue = value.toString();
+                        Date result = null;
+                        // Parsing the HTTP Date
+                        for (int i = 0; (result == null) && 
+                                 (i < formats.length); i++) {
+                            try {
+                                result = formats[i].parse(creationDateValue);
+                            } catch (ParseException e) {
+                                // Ignore
+                            }
+                        }
+                        if (result != null) {
+                            creation = result.getTime();
+                            creationDate = result;
+                        }
+                    }
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        return creationDate;
+    }
+    
+    
+    /**
+     * Creation date mutator.
+     * 
+     * @param creationDate New creation date
+     */
+    public void setCreationDate(Date creationDate) {
+        this.creation = creationDate.getTime();
+        this.creationDate = creationDate;
+        if (attributes != null)
+            attributes.put(CREATION_DATE, creationDate);
+    }
+    
+    
+    /**
+     * Get last modified time.
+     * 
+     * @return lastModified time value
+     */
+    public long getLastModified() {
+        if (lastModified != -1L)
+            return lastModified;
+        if (lastModifiedDate != null)
+            return lastModifiedDate.getTime();
+        if (attributes != null) {
+            Attribute attribute = attributes.get(LAST_MODIFIED);
+            if (attribute != null) {
+                try {
+                    Object value = attribute.get();
+                    if (value instanceof Long) {
+                        lastModified = ((Long) value).longValue();
+                    } else if (value instanceof Date) {
+                        lastModified = ((Date) value).getTime();
+                        lastModifiedDate = (Date) value;
+                    } else {
+                        String lastModifiedDateValue = value.toString();
+                        Date result = null;
+                        // Parsing the HTTP Date
+                        for (int i = 0; (result == null) && 
+                                 (i < formats.length); i++) {
+                            try {
+                                result = 
+                                    formats[i].parse(lastModifiedDateValue);
+                            } catch (ParseException e) {
+                                // Ignore
+                            }
+                        }
+                        if (result != null) {
+                            lastModified = result.getTime();
+                            lastModifiedDate = result;
+                        }
+                    }
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        return lastModified;
+    }
+    
+    
+    /**
+     * Set last modified.
+     * 
+     * @param lastModified New last modified value
+     */
+    public void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+        this.lastModifiedDate = null;
+        if (attributes != null)
+            attributes.put(LAST_MODIFIED, new Date(lastModified));
+    }
+    
+    
+    /**
+     * Get lastModified date.
+     * 
+     * @return LastModified date value
+     */
+    public Date getLastModifiedDate() {
+        if (lastModifiedDate != null)
+            return lastModifiedDate;
+        if (lastModified != -1L) {
+            lastModifiedDate = new Date(lastModified);
+            return lastModifiedDate;
+        }
+        if (attributes != null) {
+            Attribute attribute = attributes.get(LAST_MODIFIED);
+            if (attribute != null) {
+                try {
+                    Object value = attribute.get();
+                    if (value instanceof Long) {
+                        lastModified = ((Long) value).longValue();
+                        lastModifiedDate = new Date(lastModified);
+                    } else if (value instanceof Date) {
+                        lastModified = ((Date) value).getTime();
+                        lastModifiedDate = (Date) value;
+                    } else {
+                        String lastModifiedDateValue = value.toString();
+                        Date result = null;
+                        // Parsing the HTTP Date
+                        for (int i = 0; (result == null) && 
+                                 (i < formats.length); i++) {
+                            try {
+                                result = 
+                                    formats[i].parse(lastModifiedDateValue);
+                            } catch (ParseException e) {
+                                // Ignore
+                            }
+                        }
+                        if (result != null) {
+                            lastModified = result.getTime();
+                            lastModifiedDate = result;
+                        }
+                    }
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        return lastModifiedDate;
+    }
+    
+    
+    /**
+     * Last modified date mutator.
+     * 
+     * @param lastModifiedDate New last modified date
+     */
+    public void setLastModifiedDate(Date lastModifiedDate) {
+        this.lastModified = lastModifiedDate.getTime();
+        this.lastModifiedDate = lastModifiedDate;
+        if (attributes != null)
+            attributes.put(LAST_MODIFIED, lastModifiedDate);
+    }
+    
+    
+    /**
+     * @return Returns the lastModifiedHttp.
+     */
+    public String getLastModifiedHttp() {
+        if (lastModifiedHttp != null)
+            return lastModifiedHttp;
+        Date modifiedDate = getLastModifiedDate();
+        if (modifiedDate == null) {
+            modifiedDate = getCreationDate();
+        }
+        if (modifiedDate == null) {
+            modifiedDate = new Date();
+        }
+        synchronized (format) {
+            lastModifiedHttp = format.format(modifiedDate);
+        }
+        return lastModifiedHttp;
+    }
+    
+    
+    /**
+     * @param lastModifiedHttp The lastModifiedHttp to set.
+     */
+    public void setLastModifiedHttp(String lastModifiedHttp) {
+        this.lastModifiedHttp = lastModifiedHttp;
+    }
+    
+    
+    /**
+     * @return Returns the mimeType.
+     */
+    public String getMimeType() {
+        return mimeType;
+    }
+    
+    
+    /**
+     * @param mimeType The mimeType to set.
+     */
+    public void setMimeType(String mimeType) {
+        this.mimeType = mimeType;
+    }
+
+    
+    /**
+     * Get name.
+     * 
+     * @return Name value
+     */
+    public String getName() {
+        if (name != null)
+            return name;
+        if (attributes != null) {
+            Attribute attribute = attributes.get(NAME);
+            if (attribute != null) {
+                try {
+                    name = attribute.get().toString();
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        return name;
+    }
+
+
+    /**
+     * Set name.
+     * 
+     * @param name New name value
+     */
+    public void setName(String name) {
+        this.name = name;
+        if (attributes != null)
+            attributes.put(NAME, name);
+    }
+    
+    
+    /**
+     * Get resource type.
+     * 
+     * @return String resource type
+     */
+    public String getResourceType() {
+        String result = null;
+        if (attributes != null) {
+            Attribute attribute = attributes.get(TYPE);
+            if (attribute != null) {
+                try {
+                    result = attribute.get().toString();
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        if (result == null) {
+            if (collection)
+                result = COLLECTION_TYPE;
+        }
+        return result;
+    }
+    
+    
+    /**
+     * Type mutator.
+     * 
+     * @param resourceType New resource type
+     */
+    public void setResourceType(String resourceType) {
+        collection = resourceType.equals(COLLECTION_TYPE);
+        if (attributes != null)
+            attributes.put(TYPE, resourceType);
+    }
+
+
+    /**
+     * Get ETag.
+     * 
+     * @return strong ETag if available, else weak ETag. 
+     */
+    public String getETag() {
+        String result = null;
+        if (attributes != null) {
+            Attribute attribute = attributes.get(ETAG);
+            if (attribute != null) {
+                try {
+                    result = attribute.get().toString();
+                } catch (NamingException e) {
+                    // No value for the attribute
+                }
+            }
+        }
+        if (result == null) {
+            if (strongETag != null) {
+                // The strong ETag must always be calculated by the resources
+                result = strongETag;
+            } else {
+                // The weakETag is contentLength + lastModified
+                if (weakETag == null) {
+                    long contentLength = getContentLength();
+                    long lastModified = getLastModified();
+                    if ((contentLength >= 0) || (lastModified >= 0)) {
+                        weakETag = "W/\"" + contentLength + "-" +
+                                   lastModified + "\"";
+                    }
+                }
+                result = weakETag;
+            }
+        } 
+        return result;
+    }
+
+
+    /**
+     * Set strong ETag.
+     */
+    public void setETag(String eTag) {
+        this.strongETag = eTag;
+        if (attributes != null)
+            attributes.put(ETAG, eTag);
+    }
+
+    
+    /**
+     * Return the canonical path of the resource, to possibly be used for 
+     * direct file serving. Implementations which support this should override
+     * it to return the file path.
+     * 
+     * @return The canonical path of the resource
+     */
+    public String getCanonicalPath() {
+        return null;
+    }
+    
+    
+    // ----------------------------------------------------- Attributes Methods
+
+
+    /**
+     * Get attribute.
+     */
+    @Override
+    public Attribute get(String attrID) {
+        if (attributes == null) {
+            if (attrID.equals(CREATION_DATE)) {
+                Date creationDate = getCreationDate();
+                if (creationDate == null) return null;
+                return new BasicAttribute(CREATION_DATE, creationDate);
+            } else if (attrID.equals(ALTERNATE_CREATION_DATE)) {
+                Date creationDate = getCreationDate();
+                if (creationDate == null) return null;
+                return new BasicAttribute(ALTERNATE_CREATION_DATE, creationDate);
+            } else if (attrID.equals(LAST_MODIFIED)) {
+                Date lastModifiedDate = getLastModifiedDate();
+                if (lastModifiedDate == null) return null;
+                return new BasicAttribute(LAST_MODIFIED, lastModifiedDate);
+            } else if (attrID.equals(ALTERNATE_LAST_MODIFIED)) {
+                Date lastModifiedDate = getLastModifiedDate();
+                if (lastModifiedDate == null) return null;
+                return new BasicAttribute(ALTERNATE_LAST_MODIFIED, lastModifiedDate);
+            } else if (attrID.equals(NAME)) {
+                String name = getName();
+                if (name == null) return null;
+                return new BasicAttribute(NAME, name);
+            } else if (attrID.equals(TYPE)) {
+                String resourceType = getResourceType();
+                if (resourceType == null) return null;
+                return new BasicAttribute(TYPE, resourceType);
+            } else if (attrID.equals(ALTERNATE_TYPE)) {
+                String resourceType = getResourceType();
+                if (resourceType == null) return null;
+                return new BasicAttribute(ALTERNATE_TYPE, resourceType);
+            } else if (attrID.equals(CONTENT_LENGTH)) {
+                long contentLength = getContentLength();
+                if (contentLength < 0) return null;
+                return new BasicAttribute(CONTENT_LENGTH, Long.valueOf(contentLength));
+            } else if (attrID.equals(ALTERNATE_CONTENT_LENGTH)) {
+                long contentLength = getContentLength();
+                if (contentLength < 0) return null;
+                return new BasicAttribute(ALTERNATE_CONTENT_LENGTH, Long.valueOf(contentLength));
+            } else if (attrID.equals(ETAG)) {
+                String etag = getETag();
+                if (etag == null) return null;
+                return new BasicAttribute(ETAG, etag);
+            } else if (attrID.equals(ALTERNATE_ETAG)) {
+                String etag = getETag();
+                if (etag == null) return null;
+                return new BasicAttribute(ALTERNATE_ETAG, etag);
+            }
+        } else {
+            return attributes.get(attrID);
+        }
+        return null;
+    }
+    
+    
+    /**
+     * Put attribute.
+     */
+    @Override
+    public Attribute put(Attribute attribute) {
+        if (attributes == null) {
+            try {
+                return put(attribute.getID(), attribute.get());
+            } catch (NamingException e) {
+                return null;
+            }
+        } else {
+            return attributes.put(attribute);
+        }
+    }
+    
+    
+    /**
+     * Put attribute.
+     */
+    @Override
+    public Attribute put(String attrID, Object val) {
+        if (attributes == null) {
+            return null; // No reason to implement this
+        } else {
+            return attributes.put(attrID, val);
+        }
+    }
+    
+    
+    /**
+     * Remove attribute.
+     */
+    @Override
+    public Attribute remove(String attrID) {
+        if (attributes == null) {
+            return null; // No reason to implement this
+        } else {
+            return attributes.remove(attrID);
+        }
+    }
+    
+    
+    /**
+     * Get all attributes.
+     */
+    @Override
+    public NamingEnumeration<? extends Attribute> getAll() {
+        if (attributes == null) {
+            Vector<BasicAttribute> attributes = new Vector<BasicAttribute>();
+            Date creationDate = getCreationDate();
+            if (creationDate != null) {
+                attributes.addElement(new BasicAttribute
+                                      (CREATION_DATE, creationDate));
+                attributes.addElement(new BasicAttribute
+                                      (ALTERNATE_CREATION_DATE, creationDate));
+            }
+            Date lastModifiedDate = getLastModifiedDate();
+            if (lastModifiedDate != null) {
+                attributes.addElement(new BasicAttribute
+                                      (LAST_MODIFIED, lastModifiedDate));
+                attributes.addElement(new BasicAttribute
+                                      (ALTERNATE_LAST_MODIFIED, lastModifiedDate));
+            }
+            String name = getName();
+            if (name != null) {
+                attributes.addElement(new BasicAttribute(NAME, name));
+            }
+            String resourceType = getResourceType();
+            if (resourceType != null) {
+                attributes.addElement(new BasicAttribute(TYPE, resourceType));
+                attributes.addElement(new BasicAttribute(ALTERNATE_TYPE, resourceType));
+            }
+            long contentLength = getContentLength();
+            if (contentLength >= 0) {
+                Long contentLengthLong = Long.valueOf(contentLength);
+                attributes.addElement(new BasicAttribute(CONTENT_LENGTH, contentLengthLong));
+                attributes.addElement(new BasicAttribute(ALTERNATE_CONTENT_LENGTH, contentLengthLong));
+            }
+            String etag = getETag();
+            if (etag != null) {
+                attributes.addElement(new BasicAttribute(ETAG, etag));
+                attributes.addElement(new BasicAttribute(ALTERNATE_ETAG, etag));
+            }
+            return new RecyclableNamingEnumeration<BasicAttribute>(attributes);
+        } else {
+            return attributes.getAll();
+        }
+    }
+    
+    
+    /**
+     * Get all attribute IDs.
+     */
+    @Override
+    public NamingEnumeration<String> getIDs() {
+        if (attributes == null) {
+            Vector<String> attributeIDs = new Vector<String>();
+            Date creationDate = getCreationDate();
+            if (creationDate != null) {
+                attributeIDs.addElement(CREATION_DATE);
+                attributeIDs.addElement(ALTERNATE_CREATION_DATE);
+            }
+            Date lastModifiedDate = getLastModifiedDate();
+            if (lastModifiedDate != null) {
+                attributeIDs.addElement(LAST_MODIFIED);
+                attributeIDs.addElement(ALTERNATE_LAST_MODIFIED);
+            }
+            if (getName() != null) {
+                attributeIDs.addElement(NAME);
+            }
+            String resourceType = getResourceType();
+            if (resourceType != null) {
+                attributeIDs.addElement(TYPE);
+                attributeIDs.addElement(ALTERNATE_TYPE);
+            }
+            long contentLength = getContentLength();
+            if (contentLength >= 0) {
+                attributeIDs.addElement(CONTENT_LENGTH);
+                attributeIDs.addElement(ALTERNATE_CONTENT_LENGTH);
+            }
+            String etag = getETag();
+            if (etag != null) {
+                attributeIDs.addElement(ETAG);
+                attributeIDs.addElement(ALTERNATE_ETAG);
+            }
+            return new RecyclableNamingEnumeration<String>(attributeIDs);
+        } else {
+            return attributes.getIDs();
+        }
+    }
+    
+    
+    /**
+     * Retrieves the number of attributes in the attribute set.
+     */
+    @Override
+    public int size() {
+        if (attributes == null) {
+            int size = 0;
+            if (getCreationDate() != null) size += 2;
+            if (getLastModifiedDate() != null) size += 2;
+            if (getName() != null) size++;
+            if (getResourceType() != null) size += 2;
+            if (getContentLength() >= 0) size += 2;
+            if (getETag() != null) size += 2;
+            return size;
+        } else {
+            return attributes.size();
+        }
+    }
+    
+    
+    /**
+     * Clone the attributes object (WARNING: fake cloning).
+     */
+    @Override
+    public Object clone() {
+        return this;
+    }
+    
+    
+    /**
+     * Case sensitivity.
+     */
+    @Override
+    public boolean isCaseIgnored() {
+        return false;
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/ResourceCache.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ResourceCache.java
new file mode 100644
index 0000000..037d63e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/ResourceCache.java
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+package org.apache.naming.resources;
+
+import java.util.HashMap;
+import java.util.Random;
+
+
+/**
+ * Implements a special purpose cache.
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @version $Revision: 1.1 $
+ */
+public class ResourceCache {
+    
+    
+    // ----------------------------------------------------------- Constructors
+    
+    
+    public ResourceCache() {
+        // NO-OP
+    }
+    
+    
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Random generator used to determine elements to free.
+     */
+    protected Random random = new Random();
+
+
+    /**
+     * Cache.
+     * Path -> Cache entry.
+     */
+    protected CacheEntry[] cache = new CacheEntry[0];
+
+
+    /**
+     * Not found cache.
+     */
+    protected HashMap<String,CacheEntry> notFoundCache =
+        new HashMap<String,CacheEntry>();
+
+
+    /**
+     * Max size of resources which will have their content cached.
+     */
+    protected int cacheMaxSize = 10240; // 10 MB
+
+
+    /**
+     * Max amount of removals during a make space.
+     */
+    protected int maxAllocateIterations = 20;
+
+
+    /**
+     * Entry hit ratio at which an entry will never be removed from the cache.
+     * Compared with entry.access / hitsCount
+     */
+    protected long desiredEntryAccessRatio = 3;
+
+
+    /**
+     * Spare amount of not found entries.
+     */
+    protected int spareNotFoundEntries = 500;
+
+
+    /**
+     * Current cache size in KB.
+     */
+    protected int cacheSize = 0;
+
+
+    /**
+     * Number of accesses to the cache.
+     */
+    protected long accessCount = 0;
+
+
+    /**
+     * Number of cache hits.
+     */
+    protected long hitsCount = 0;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the access count.
+     * Note: Update is not synced, so the number may not be completely 
+     * accurate.
+     */
+    public long getAccessCount() {
+        return accessCount;
+    }
+
+
+    /**
+     * Return the maximum size of the cache in KB.
+     */
+    public int getCacheMaxSize() {
+        return cacheMaxSize;
+    }
+
+
+    /**
+     * Set the maximum size of the cache in KB.
+     */
+    public void setCacheMaxSize(int cacheMaxSize) {
+        this.cacheMaxSize = cacheMaxSize;
+    }
+
+
+    /**
+     * Return the current cache size in KB.
+     */
+    public int getCacheSize() {
+        return cacheSize;
+    }
+
+
+    /**
+     * Return desired entry access ratio.
+     */
+    public long getDesiredEntryAccessRatio() {
+        return desiredEntryAccessRatio;
+    }
+
+
+    /**
+     * Set the desired entry access ratio.
+     */
+    public void setDesiredEntryAccessRatio(long desiredEntryAccessRatio) {
+        this.desiredEntryAccessRatio = desiredEntryAccessRatio;
+    }
+
+
+    /**
+     * Return the number of cache hits.
+     * Note: Update is not synced, so the number may not be completely 
+     * accurate.
+     */
+    public long getHitsCount() {
+        return hitsCount;
+    }
+
+
+    /**
+     * Return the maximum amount of iterations during a space allocation.
+     */
+    public int getMaxAllocateIterations() {
+        return maxAllocateIterations;
+    }
+
+
+    /**
+     * Set the maximum amount of iterations during a space allocation.
+     */
+    public void setMaxAllocateIterations(int maxAllocateIterations) {
+        this.maxAllocateIterations = maxAllocateIterations;
+    }
+
+
+    /**
+     * Return the amount of spare not found entries.
+     */
+    public int getSpareNotFoundEntries() {
+        return spareNotFoundEntries;
+    }
+
+
+    /**
+     * Set the amount of spare not found entries.
+     */
+    public void setSpareNotFoundEntries(int spareNotFoundEntries) {
+        this.spareNotFoundEntries = spareNotFoundEntries;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    public boolean allocate(int space) {
+
+        int toFree = space - (cacheMaxSize - cacheSize);
+
+        if (toFree <= 0) {
+            return true;
+        }
+
+        // Increase the amount to free so that allocate won't have to run right
+        // away again
+        toFree += (cacheMaxSize / 20);
+
+        int size = notFoundCache.size();
+        if (size > spareNotFoundEntries) {
+            notFoundCache.clear();
+            cacheSize -= size;
+            toFree -= size;
+        }
+
+        if (toFree <= 0) {
+            return true;
+        }
+
+        int attempts = 0;
+        int entriesFound = 0;
+        long totalSpace = 0;
+        int[] toRemove = new int[maxAllocateIterations];
+        while (toFree > 0) {
+            if (attempts == maxAllocateIterations) {
+                // Give up, no changes are made to the current cache
+                return false;
+            }
+            if (toFree > 0) {
+                // Randomly select an entry in the array
+                int entryPos = -1;
+                boolean unique = false;
+                while (!unique) {
+                    unique = true;
+                    entryPos = random.nextInt(cache.length) ;
+                    // Guarantee uniqueness
+                    for (int i = 0; i < entriesFound; i++) {
+                        if (toRemove[i] == entryPos) {
+                            unique = false;
+                        }
+                    }
+                }
+                long entryAccessRatio = 
+                    ((cache[entryPos].accessCount * 100) / accessCount);
+                if (entryAccessRatio < desiredEntryAccessRatio) {
+                    toRemove[entriesFound] = entryPos;
+                    totalSpace += cache[entryPos].size;
+                    toFree -= cache[entryPos].size;
+                    entriesFound++;
+                }
+            }
+            attempts++;
+        }
+
+        // Now remove the selected entries
+        java.util.Arrays.sort(toRemove, 0, entriesFound);
+        CacheEntry[] newCache = new CacheEntry[cache.length - entriesFound];
+        int pos = 0;
+        int n = -1;
+        if (entriesFound > 0) {
+            n = toRemove[0];
+            for (int i = 0; i < cache.length; i++) {
+                if (i == n) {
+                    if ((pos + 1) < entriesFound) {
+                        n = toRemove[pos + 1];
+                        pos++;
+                    } else {
+                        pos++;
+                        n = -1;
+                    }
+                } else {
+                    newCache[i - pos] = cache[i];
+                }
+            }
+        }
+        cache = newCache;
+        cacheSize -= totalSpace;
+
+        return true;
+
+    }
+
+
+    public CacheEntry lookup(String name) {
+
+        CacheEntry cacheEntry = null;
+        CacheEntry[] currentCache = cache;
+        accessCount++;
+        int pos = find(currentCache, name);
+        if ((pos != -1) && (name.equals(currentCache[pos].name))) {
+            cacheEntry = currentCache[pos];
+        }
+        if (cacheEntry == null) {
+            try {
+                cacheEntry = notFoundCache.get(name);
+            } catch (Exception e) {
+                // Ignore: the reliability of this lookup is not critical
+            }
+        }
+        if (cacheEntry != null) {
+            hitsCount++;
+        }
+        return cacheEntry;
+
+    }
+
+
+    public void load(CacheEntry entry) {
+        if (entry.exists) {
+            if (insertCache(entry)) {
+                cacheSize += entry.size;
+            }
+        } else {
+            int sizeIncrement = (notFoundCache.get(entry.name) == null) ? 1 : 0;
+            notFoundCache.put(entry.name, entry);
+            cacheSize += sizeIncrement;
+        }
+    }
+
+
+    public boolean unload(String name) {
+        CacheEntry removedEntry = removeCache(name);
+        if (removedEntry != null) {
+            cacheSize -= removedEntry.size;
+            return true;
+        } else if (notFoundCache.remove(name) != null) {
+            cacheSize--;
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Find a map element given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int find(CacheEntry[] map, String name) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        if (name.compareTo(map[0].name) < 0) {
+            return -1;
+        }
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) >>> 1;
+            int result = name.compareTo(map[i].name);
+            if (result > 0) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = name.compareTo(map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array, and prevent
+     * duplicates.
+     */
+    private final boolean insertCache(CacheEntry newElement) {
+        CacheEntry[] oldCache = cache;
+        int pos = find(oldCache, newElement.name);
+        if ((pos != -1) && (newElement.name.equals(oldCache[pos].name))) {
+            return false;
+        }
+        CacheEntry[] newCache = new CacheEntry[cache.length + 1];
+        System.arraycopy(oldCache, 0, newCache, 0, pos + 1);
+        newCache[pos + 1] = newElement;
+        System.arraycopy
+            (oldCache, pos + 1, newCache, pos + 2, oldCache.length - pos - 1);
+        cache = newCache;
+        return true;
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array.
+     */
+    private final CacheEntry removeCache(String name) {
+        CacheEntry[] oldCache = cache;
+        int pos = find(oldCache, name);
+        if ((pos != -1) && (name.equals(oldCache[pos].name))) {
+            CacheEntry[] newCache = new CacheEntry[cache.length - 1];
+            System.arraycopy(oldCache, 0, newCache, 0, pos);
+            System.arraycopy(oldCache, pos + 1, newCache, pos, 
+                             oldCache.length - pos - 1);
+            cache = newCache;
+            return oldCache[pos];
+        }
+        return null;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/VirtualDirContext.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/VirtualDirContext.java
new file mode 100644
index 0000000..7de7c2d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/VirtualDirContext.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.naming.resources;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+
+import org.apache.naming.NamingEntry;
+
+/**
+ * Extended FileDirContext implementation that will allow loading of tld files
+ * from the META-INF directory (or subdirectories) in classpath. This will fully
+ * mimic the behavior of compressed jars also when using unjarred resources. Tld
+ * files can be loaded indifferently from WEB-INF webapp dir (or subdirs) or
+ * from META-INF dir from jars available in the classpath: using this DirContext
+ * implementation you will be able to use unexpanded jars during development and
+ * to make any tld in them virtually available to the webapp.
+ *
+ * Sample context xml configuration:
+ *
+ * <code>
+ * &lt;Context docBase="\webapps\mydocbase">
+ *   &lt;Resources className="org.apache.naming.resources.VirtualDirContext"
+ *              virtualClasspath="\dir\classes;\somedir\somejar.jar"/>
+ * &lt;/Resources>
+ * </code>
+ *
+ *
+ * <strong>This is not meant to be used for production.
+ * Its meant to ease development with IDE's without the
+ * need for fully republishing jars in WEB-INF/lib</strong>
+ *
+ *
+ * @author Fabrizio Giustina
+ * @version $Id: VirtualDirContext.java,v 1.1 2011/06/28 21:14:09 rherrmann Exp $
+ */
+public class VirtualDirContext extends FileDirContext {
+
+    /**
+     * Map containing generated virtual names for tld files under WEB-INF and
+     * the actual file reference.
+     */
+    private Map<String, File> virtualMappings;
+
+    /**
+     * Map containing a mapping for tag files that should be loaded from the
+     * META-INF dir of referenced jar files.
+     */
+    private Map<String, File> tagfileMappings;
+
+    /**
+     * <code>;</code> separated list of virtual path elements.
+     */
+    private String virtualClasspath;
+
+    /**
+     * <code>virtualClasspath</code> attribute that will be automatically set
+     * from the <code>Context</code> <code>virtualClasspath</code> attribute
+     * from the context xml file.
+     * @param path <code>;</code> separated list of path elements.
+     */
+    public void setVirtualClasspath(String path) {
+        virtualClasspath = path;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void allocate() {
+        super.allocate();
+
+        virtualMappings = new Hashtable<String, File>();
+        tagfileMappings = new Hashtable<String, File>();
+
+        // looks into any META-INF dir found in classpath entries for tld files.
+        StringTokenizer tkn = new StringTokenizer(virtualClasspath, ";");
+        while (tkn.hasMoreTokens()) {
+            File file = new File(tkn.nextToken(), "META-INF");
+
+            if (!file.exists() || !file.isDirectory()) {
+                continue;
+            }
+            scanForTlds(file);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void release() {
+        super.release();
+        virtualMappings = null;
+    }
+
+    @Override
+    public Attributes getAttributes(String name) throws NamingException {
+
+        // handle "virtual" tlds
+        if (name.startsWith("/WEB-INF/") && name.endsWith(".tld")) {
+            String tldName = name.substring(name.lastIndexOf("/") + 1);
+            if (virtualMappings.containsKey(tldName)) {
+                return new FileResourceAttributes(virtualMappings.get(tldName));
+            }
+        } else if (name.startsWith("/META-INF/tags") && name.endsWith(".tag")
+                || name.endsWith(".tagx")) {
+
+            // already loaded tag file
+            if (tagfileMappings.containsKey(name)) {
+                return new FileResourceAttributes(tagfileMappings.get(name));
+            }
+
+            // unknown tagfile, search for it in virtualClasspath
+            StringTokenizer tkn = new StringTokenizer(virtualClasspath, ";");
+            while (tkn.hasMoreTokens()) {
+                File file = new File(tkn.nextToken(), name);
+                if (file.exists()) {
+                    tagfileMappings.put(name, file);
+                    return new FileResourceAttributes(file);
+                }
+            }
+        }
+
+        return super.getAttributes(name);
+    }
+
+    @Override
+    protected List<NamingEntry> list(File file) {
+        List<NamingEntry> entries = super.list(file);
+
+        // adds virtual tlds for WEB-INF listing
+        if ("WEB-INF".equals(file.getName())) {
+            entries.addAll(getVirtualNamingEntries());
+        }
+
+        return entries;
+    }
+
+    @Override
+    protected Object doLookup(String name) {
+
+        // handle "virtual" tlds
+        if (name.startsWith("/WEB-INF/") && name.endsWith(".tld")) {
+            String tldName = name.substring(name.lastIndexOf("/") + 1);
+            if (virtualMappings.containsKey(tldName)) {
+                return new FileResource(virtualMappings.get(tldName));
+            }
+        } else if (name.startsWith("/META-INF/tags") && name.endsWith(".tag")
+                || name.endsWith(".tagx")) {
+
+            // already loaded tag file: we are sure that getAttributes() has
+            // already been called if we are here
+            File tagFile = tagfileMappings.get(name);
+            if (tagFile != null) {
+                return new FileResource(tagFile);
+            }
+        }
+
+        return super.doLookup(name);
+    }
+
+    /**
+     * Scan a given dir for tld files. Any found tld will be added to the
+     * virtualMappings.
+     * @param dir Dir to scan for tlds
+     */
+    private void scanForTlds(File dir) {
+
+        File[] files = dir.listFiles();
+        for (int j = 0; j < files.length; j++) {
+            File file = files[j];
+
+            if (file.isDirectory()) {
+                scanForTlds(file);
+            } else if (file.getName().endsWith(".tld")) {
+                // just generate a random name using the current timestamp, name
+                // doesn't matter since it needs to be referenced by URI
+                String virtualTldName = "~" + System.currentTimeMillis() + "~"
+                        + file.getName();
+                virtualMappings.put(virtualTldName, file);
+            }
+        }
+
+    }
+
+    /**
+     * Returns a list of virtual naming entries.
+     * @return list of naming entries, containing tlds in virtualMappings
+     */
+    private List<NamingEntry> getVirtualNamingEntries() {
+        List<NamingEntry> virtual = new ArrayList<NamingEntry>();
+
+        for (String name : virtualMappings.keySet()) {
+
+            File file = virtualMappings.get(name);
+            NamingEntry entry = new NamingEntry(name, new FileResource(file),
+                    NamingEntry.ENTRY);
+            virtual.add(entry);
+        }
+        return virtual;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/WARDirContext.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/WARDirContext.java
new file mode 100644
index 0000000..aebecf7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/WARDirContext.java
@@ -0,0 +1,1003 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming.resources;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import javax.naming.CompositeName;
+import javax.naming.InvalidNameException;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.OperationNotSupportedException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.apache.naming.NamingContextEnumeration;
+import org.apache.naming.NamingEntry;
+
+/**
+ * WAR Directory Context implementation.
+ *
+ * @author Remy Maucherat
+ * @version $Id: WARDirContext.java,v 1.1 2011/06/28 21:14:10 rherrmann Exp $
+ */
+
+public class WARDirContext extends BaseDirContext {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( WARDirContext.class );
+    
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Builds a WAR directory context using the given environment.
+     */
+    public WARDirContext() {
+        super();
+    }
+
+
+    /**
+     * Builds a WAR directory context using the given environment.
+     */
+    public WARDirContext(Hashtable<String,Object> env) {
+        super(env);
+    }
+
+
+    /**
+     * Constructor used for returning fake sub-contexts or for accessing
+     * META-INF/resources locations in bundled JAR files.
+     */
+    protected WARDirContext(ZipFile base, Entry entries) {
+        this.base = base;
+        this.entries = entries;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The WAR file.
+     */
+    protected ZipFile base = null;
+
+
+    /**
+     * WAR entries.
+     */
+    protected Entry entries = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Set the document root.
+     * 
+     * @param docBase The new document root
+     * 
+     * @exception IllegalArgumentException if the specified value is not
+     *  supported by this implementation
+     * @exception IllegalArgumentException if this would create a
+     *  malformed URL
+     */
+    @Override
+    public void setDocBase(String docBase) {
+
+        // Validate the format of the proposed document root
+        if (docBase == null)
+            throw new IllegalArgumentException
+                (sm.getString("resources.null"));
+        if (!(docBase.endsWith(".war")))
+            throw new IllegalArgumentException
+                (sm.getString("warResources.notWar"));
+
+        // Calculate a File object referencing this document base directory
+        File base = new File(docBase);
+
+        // Validate that the document base is an existing directory
+        if (!base.exists() || !base.canRead() || base.isDirectory())
+            throw new IllegalArgumentException
+                (sm.getString("warResources.invalidWar", docBase));
+        try {
+            this.base = new ZipFile(base);
+        } catch (Exception e) {
+            throw new IllegalArgumentException
+                (sm.getString("warResources.invalidWar", e.getMessage()));
+        }
+        super.setDocBase(docBase);
+
+        loadEntries();
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Release any resources allocated for this directory context.
+     */
+    @Override
+    public void release() {
+
+        entries = null;
+        if (base != null) {
+            try {
+                base.close();
+            } catch (IOException e) {
+                log.warn
+                    ("Exception closing WAR File " + base.getName(), e);
+            }
+        }
+        base = null;
+        super.release();
+
+    }
+
+    /**
+     * Return the real path for a given virtual path, if possible; otherwise
+     * return <code>null</code>.
+     *
+     * @param path The path to the desired resource
+     */
+    @Override
+    protected String doGetRealPath(String path) {
+        return null;
+    }
+
+
+    // -------------------------------------------------------- Context Methods
+
+
+    /**
+     * Retrieves the named object.
+     * 
+     * @param strName the name of the object to look up
+     * @return the object bound to name
+     */
+    @Override
+    protected Object doLookup(String strName) {
+
+        Name name;
+        try {
+            name = getEscapedJndiName(strName);
+        } catch (InvalidNameException e) {
+            log.info(sm.getString("resources.invalidName", strName), e);
+            return null;
+        }
+
+        if (name.isEmpty())
+            return this;
+        Entry entry = treeLookup(name);
+        if (entry == null)
+            return null;
+            
+        ZipEntry zipEntry = entry.getEntry();
+        if (zipEntry.isDirectory())
+            return new WARDirContext(base, entry);
+        else
+            return new WARResource(entry.getEntry());
+    }
+
+
+    /**
+     * JNDI treats ' and " as reserved characters therefore they need to be
+     * escaped as part of converting file names to JNDI names. Note that while
+     * ' can be used in Windows and Unix file names, " is only valid on Unix.
+     * This method assumes that the string is currently unquoted.
+     * 
+     * @return  A valid JNDI name
+     * @throws InvalidNameException 
+     */
+    private Name getEscapedJndiName(String name) throws InvalidNameException {
+        return new CompositeName(name.replace("'", "\\'").replace("\"", ""));
+    }
+
+    /**
+     * Unbinds the named object. Removes the terminal atomic name in name 
+     * from the target context--that named by all but the terminal atomic 
+     * part of name.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic 
+     * name is not bound in the target context, but throws 
+     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * 
+     * @param name the name to bind; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void unbind(String name)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Binds a new name to the object bound to an old name, and unbinds the 
+     * old name. Both names are relative to this context. Any attributes 
+     * associated with the old name become associated with the new name. 
+     * Intermediate contexts of the old name are not changed.
+     * 
+     * @param oldName the name of the existing binding; may not be empty
+     * @param newName the name of the new binding; may not be empty
+     * @exception javax.naming.NameAlreadyBoundException if newName is already
+     * bound
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rename(String oldName, String newName)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the class 
+     * names of objects bound to them. The contents of any subcontexts are 
+     * not included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on 
+     * an enumeration previously returned is undefined.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the names and class names of the bindings in 
+     * this context. Each element of the enumeration is of type NameClassPair.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<NameClassPair> list(String name)
+        throws NamingException {
+        return list(getEscapedJndiName(name));
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the class 
+     * names of objects bound to them. The contents of any subcontexts are 
+     * not included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on 
+     * an enumeration previously returned is undefined.
+     * 
+     * @param name the name of the context to list
+     * @return an enumeration of the names and class names of the bindings in 
+     * this context. Each element of the enumeration is of type NameClassPair.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<NameClassPair> list(Name name)
+        throws NamingException {
+        if (name.isEmpty())
+            return new NamingContextEnumeration(list(entries).iterator());
+        Entry entry = treeLookup(name);
+        if (entry == null)
+            throw new NameNotFoundException
+                (sm.getString("resources.notFound", name));
+        return new NamingContextEnumeration(list(entry).iterator());
+    }
+
+
+    /**
+     * Enumerates the names bound in the named context, along with the 
+     * objects bound to them. The contents of any subcontexts are not 
+     * included.
+     * <p>
+     * If a binding is added to or removed from this context, its effect on 
+     * an enumeration previously returned is undefined.
+     * 
+     * @param strName the name of the context to list
+     * @return an enumeration of the bindings in this context. 
+     * Each element of the enumeration is of type Binding.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    protected List<NamingEntry> doListBindings(String strName)
+        throws NamingException {
+        
+        Name name = getEscapedJndiName(strName);
+
+        if (name.isEmpty())
+            return list(entries);
+
+        Entry entry = treeLookup(name);
+        if (entry == null)
+            return null;
+        
+        return list(entry);
+    }
+
+
+    /**
+     * Destroys the named context and removes it from the namespace. Any 
+     * attributes associated with the name are also removed. Intermediate 
+     * contexts are not destroyed.
+     * <p>
+     * This method is idempotent. It succeeds even if the terminal atomic 
+     * name is not bound in the target context, but throws 
+     * NameNotFoundException if any of the intermediate contexts do not exist. 
+     * 
+     * In a federated naming system, a context from one naming system may be 
+     * bound to a name in another. One can subsequently look up and perform 
+     * operations on the foreign context using a composite name. However, an 
+     * attempt destroy the context using this composite name will fail with 
+     * NotContextException, because the foreign context is not a "subcontext" 
+     * of the context in which it is bound. Instead, use unbind() to remove 
+     * the binding of the foreign context. Destroying the foreign context 
+     * requires that the destroySubcontext() be performed on a context from 
+     * the foreign context's "native" naming system.
+     * 
+     * @param name the name of the context to be destroyed; may not be empty
+     * @exception NameNotFoundException if an intermediate context does not 
+     * exist
+     * @exception javax.naming.NotContextException if the name is bound but does
+     * not name a context, or does not name a context of the appropriate type
+     */
+    @Override
+    public void destroySubcontext(String name)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Retrieves the named object, following links except for the terminal 
+     * atomic component of the name. If the object bound to name is not a 
+     * link, returns the object itself.
+     * 
+     * @param name the name of the object to look up
+     * @return the object bound to name, not following the terminal link 
+     * (if any).
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Object lookupLink(String name)
+        throws NamingException {
+        // Note : Links are not supported
+        return lookup(name);
+    }
+
+
+    /**
+     * Retrieves the full name of this context within its own namespace.
+     * <p>
+     * Many naming services have a notion of a "full name" for objects in 
+     * their respective namespaces. For example, an LDAP entry has a 
+     * distinguished name, and a DNS record has a fully qualified name. This 
+     * method allows the client application to retrieve this name. The string 
+     * returned by this method is not a JNDI composite name and should not be 
+     * passed directly to context methods. In naming systems for which the 
+     * notion of full name does not make sense, 
+     * OperationNotSupportedException is thrown.
+     * 
+     * @return this context's name in its own namespace; never null
+     * @exception OperationNotSupportedException if the naming system does 
+     * not have the notion of a full name
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public String getNameInNamespace()
+        throws NamingException {
+        return docBase;
+    }
+
+
+    // ----------------------------------------------------- DirContext Methods
+
+
+    /**
+     * Retrieves selected attributes associated with a named object. 
+     * See the class description regarding attribute models, attribute type 
+     * names, and operational attributes.
+     * 
+     * @return the requested attributes; never null
+     * @param name the name of the object from which to retrieve attributes
+     * @param attrIds the identifiers of the attributes to retrieve. null 
+     * indicates that all attributes should be retrieved; an empty array 
+     * indicates that none should be retrieved
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    protected Attributes doGetAttributes(String name, String[] attrIds)
+        throws NamingException {
+        return getAttributes(getEscapedJndiName(name), attrIds);
+    }
+
+
+    /**
+     * Retrieves all of the attributes associated with a named object. 
+     * 
+     * @return the set of attributes associated with name. 
+     * Returns an empty attribute set if name has no attributes; never null.
+     * @param name the name of the object from which to retrieve attributes
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public Attributes getAttributes(Name name, String[] attrIds)
+        throws NamingException {
+        
+        Entry entry = null;
+        if (name.isEmpty())
+            entry = entries;
+        else
+            entry = treeLookup(name);
+        if (entry == null)
+            return null;
+        
+        ZipEntry zipEntry = entry.getEntry();
+
+        ResourceAttributes attrs = new ResourceAttributes();
+        attrs.setCreationDate(new Date(zipEntry.getTime()));
+        attrs.setName(entry.getName());
+        if (!zipEntry.isDirectory())
+            attrs.setResourceType("");
+        else
+            attrs.setCollection(true);
+        attrs.setContentLength(zipEntry.getSize());
+        attrs.setLastModified(zipEntry.getTime());
+        
+        return attrs;
+        
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object. The order of 
+     * the modifications is not specified. Where possible, the modifications 
+     * are performed atomically.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mod_op the modification operation, one of: ADD_ATTRIBUTE, 
+     * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
+     * @param attrs the attributes to be used for the modification; may not 
+     * be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(String name, int mod_op, Attributes attrs)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Modifies the attributes associated with a named object using an an 
+     * ordered list of modifications. The modifications are performed in the 
+     * order specified. Each modification specifies a modification operation 
+     * code and an attribute on which to operate. Where possible, the 
+     * modifications are performed atomically.
+     * 
+     * @param name the name of the object whose attributes will be updated
+     * @param mods an ordered sequence of modifications to be performed; may 
+     * not be null
+     * @exception javax.naming.directory.AttributeModificationException if the
+     * modification cannot be completed successfully
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void modifyAttributes(String name, ModificationItem[] mods)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes. If attrs 
+     * is null, the resulting binding will have the attributes associated 
+     * with obj if obj is a DirContext, and no attributes otherwise. If attrs 
+     * is non-null, the resulting binding will have attrs as its attributes; 
+     * any attributes associated with obj are ignored.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void bind(String name, Object obj, Attributes attrs)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Binds a name to an object, along with associated attributes, 
+     * overwriting any existing binding. If attrs is null and obj is a 
+     * DirContext, the attributes from obj are used. If attrs is null and obj 
+     * is not a DirContext, any existing attributes associated with the object
+     * already bound in the directory remain unchanged. If attrs is non-null, 
+     * any existing attributes associated with the object already bound in 
+     * the directory are removed and attrs is associated with the named 
+     * object. If obj is a DirContext and attrs is non-null, the attributes 
+     * of obj are ignored.
+     * 
+     * @param name the name to bind; may not be empty
+     * @param obj the object to bind; possibly null
+     * @param attrs the attributes to associate with the binding
+     * @exception javax.naming.directory.InvalidAttributesException if some
+     * "mandatory" attributes of the binding are not supplied
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rebind(String name, Object obj, Attributes attrs)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Creates and binds a new context, along with associated attributes. 
+     * This method creates a new subcontext with the given name, binds it in 
+     * the target context (that named by all but terminal atomic component of 
+     * the name), and associates the supplied attributes with the newly 
+     * created object. All intermediate and target contexts must already 
+     * exist. If attrs is null, this method is equivalent to 
+     * Context.createSubcontext().
+     * 
+     * @param name the name of the context to create; may not be empty
+     * @param attrs the attributes to associate with the newly created context
+     * @return the newly created context
+     * @exception javax.naming.NameAlreadyBoundException if name is already
+     * bound
+     * @exception javax.naming.directory.InvalidAttributesException if attrs
+     * does not contain all the mandatory attributes required for creation
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext createSubcontext(String name, Attributes attrs)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Retrieves the schema associated with the named object. The schema 
+     * describes rules regarding the structure of the namespace and the 
+     * attributes stored within it. The schema specifies what types of 
+     * objects can be added to the directory and where they can be added; 
+     * what mandatory and optional attributes an object can have. The range 
+     * of support for schemas is directory-specific.
+     * 
+     * @param name the name of the object whose schema is to be retrieved
+     * @return the schema associated with the context; never null
+     * @exception OperationNotSupportedException if schema not supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchema(String name)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Retrieves a context containing the schema objects of the named 
+     * object's class definitions.
+     * 
+     * @param name the name of the object whose object class definition is to 
+     * be retrieved
+     * @return the DirContext containing the named object's class 
+     * definitions; never null
+     * @exception OperationNotSupportedException if schema not supported
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public DirContext getSchemaClassDefinition(String name)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes, and retrieves selected attributes. The search is 
+     * performed using the default SearchControls settings.
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @param attributesToReturn the attributes to return. null indicates 
+     * that all attributes are to be returned; an empty array indicates that 
+     * none are to be returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            Attributes matchingAttributes, String[] attributesToReturn)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Searches in a single context for objects that contain a specified set 
+     * of attributes. This method returns all the attributes of such objects. 
+     * It is equivalent to supplying null as the atributesToReturn parameter 
+     * to the method search(Name, Attributes, String[]).
+     * 
+     * @param name the name of the context to search
+     * @param matchingAttributes the attributes to search for. If empty or 
+     * null, all objects in the target context are returned.
+     * @return a non-null enumeration of SearchResult objects. Each 
+     * SearchResult contains the attributes identified by attributesToReturn 
+     * and the name of the corresponding object, named relative to the 
+     * context named by name.
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name,
+            Attributes matchingAttributes) throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filter the filter expression to use for the search; may not be 
+     * null
+     * @param cons the search controls that control the search. If null, 
+     * the default search controls are used (equivalent to 
+     * (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy 
+     * the filter; never null
+     * @exception javax.naming.directory.InvalidSearchFilterException if the
+     * search filter specified is not supported or understood by the underlying
+     * directory
+     * @exception javax.naming.directory.InvalidSearchControlsException if the
+     * search controls contain invalid settings
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name, String filter, 
+                                    SearchControls cons)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    /**
+     * Searches in the named context or object for entries that satisfy the 
+     * given search filter. Performs the search as specified by the search 
+     * controls.
+     * 
+     * @param name the name of the context or object to search
+     * @param filterExpr the filter expression to use for the search. 
+     * The expression may contain variables of the form "{i}" where i is a 
+     * nonnegative integer. May not be null.
+     * @param filterArgs the array of arguments to substitute for the 
+     * variables in filterExpr. The value of filterArgs[i] will replace each 
+     * occurrence of "{i}". If null, equivalent to an empty array.
+     * @param cons the search controls that control the search. If null, the 
+     * default search controls are used (equivalent to (new SearchControls())).
+     * @return an enumeration of SearchResults of the objects that satisfy the 
+     * filter; never null
+     * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i} 
+     * expressions where i is outside the bounds of the array filterArgs
+     * @exception javax.naming.directory.InvalidSearchControlsException if cons
+     * contains invalid settings
+     * @exception javax.naming.directory.InvalidSearchFilterException if
+     * filterExpr with filterArgs represents an invalid search filter
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public NamingEnumeration<SearchResult> search(String name, String filterExpr, 
+                                    Object[] filterArgs, SearchControls cons)
+        throws NamingException {
+        throw new OperationNotSupportedException();
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Normalize the name of an entry read from the Zip.
+     */
+    protected String normalize(ZipEntry entry) {
+
+        String result = "/" + entry.getName();
+        if (entry.isDirectory()) {
+            result = result.substring(0, result.length() - 1);
+        }
+        return result;
+
+    }
+
+
+    /**
+     * Constructs a tree of the entries contained in a WAR file.
+     */
+    protected void loadEntries() {
+
+        try {
+
+            Enumeration<? extends ZipEntry> entryList = base.entries();
+            entries = new Entry("/", new ZipEntry("/"));
+            
+            while (entryList.hasMoreElements()) {
+                
+                ZipEntry entry = entryList.nextElement();
+                String name = normalize(entry);
+                int pos = name.lastIndexOf('/');
+                // Check that parent entries exist and, if not, create them.
+                // This fixes a bug for war files that don't record separate
+                // zip entries for the directories.
+                int currentPos = -1;
+                int lastPos = 0;
+                while ((currentPos = name.indexOf('/', lastPos)) != -1) {
+                    Name parentName = getEscapedJndiName(name.substring(0, lastPos));
+                    Name childName = getEscapedJndiName(name.substring(0, currentPos));
+                    String entryName = name.substring(lastPos, currentPos);
+                    // Parent should have been created in last cycle through
+                    // this loop
+                    Entry parent = treeLookup(parentName);
+                    Entry child = treeLookup(childName);
+                    if (child == null) {
+                        // Create a new entry for missing entry and strip off
+                        // the leading '/' character and appended on by the
+                        // normalize method and add '/' character to end to
+                        // signify that it is a directory entry
+                        String zipName = name.substring(1, currentPos) + "/";
+                        child = new Entry(entryName, new ZipEntry(zipName));
+                        if (parent != null)
+                            parent.addChild(child);
+                    }
+                    // Increment lastPos
+                    lastPos = currentPos + 1;
+                }
+                String entryName = name.substring(pos + 1, name.length());
+                Name compositeName = getEscapedJndiName(name.substring(0, pos));
+                Entry parent = treeLookup(compositeName);
+                Entry child = new Entry(entryName, entry);
+                if (parent != null)
+                    parent.addChild(child);
+                
+            }
+
+        } catch (Exception e) {
+            // Ignore
+        }
+
+    }
+
+
+    /**
+     * Entry tree lookup.
+     */
+    protected Entry treeLookup(Name name) {
+        if (name.isEmpty() || entries == null)
+            return entries;
+        Entry currentEntry = entries;
+        for (int i = 0; i < name.size(); i++) {
+            if (name.get(i).length() == 0)
+                continue;
+            currentEntry = currentEntry.getChild(name.get(i));
+            if (currentEntry == null)
+                return null;
+        }
+        return currentEntry;
+    }
+
+
+    /**
+     * List children as objects.
+     */
+    protected ArrayList<NamingEntry> list(Entry entry) {
+        
+        ArrayList<NamingEntry> entries = new ArrayList<NamingEntry>();
+        Entry[] children = entry.getChildren();
+        Arrays.sort(children);
+        NamingEntry namingEntry = null;
+        
+        for (int i = 0; i < children.length; i++) {
+            ZipEntry current = children[i].getEntry();
+            Object object = null;
+            if (current.isDirectory()) {
+                object = new WARDirContext(base, children[i]);
+            } else {
+                object = new WARResource(current);
+            }
+            namingEntry = new NamingEntry
+                (children[i].getName(), object, NamingEntry.ENTRY);
+            entries.add(namingEntry);
+        }
+        
+        return entries;
+        
+    }
+
+
+    // ---------------------------------------------------- Entries Inner Class
+
+
+    /**
+     * Entries structure.
+     */
+    protected static class Entry implements Comparable<Object> {
+
+
+        // -------------------------------------------------------- Constructor
+        
+        
+        public Entry(String name, ZipEntry entry) {
+            this.name = name;
+            this.entry = entry;
+        }
+        
+        
+        // --------------------------------------------------- Member Variables
+        
+        
+        protected String name = null;
+        
+        
+        protected ZipEntry entry = null;
+        
+        
+        protected Entry children[] = new Entry[0];
+        
+        
+        // ----------------------------------------------------- Public Methods
+        
+        
+        @Override
+        public int compareTo(Object o) {
+            if (!(o instanceof Entry))
+                return (+1);
+            return name.compareTo(((Entry) o).getName());
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Entry))
+                return false;
+            return name.equals(((Entry) o).getName());
+        }
+        
+        @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+
+        public ZipEntry getEntry() {
+            return entry;
+        }
+        
+        
+        public String getName() {
+            return name;
+        }
+        
+        
+        public void addChild(Entry entry) {
+            Entry[] newChildren = new Entry[children.length + 1];
+            for (int i = 0; i < children.length; i++)
+                newChildren[i] = children[i];
+            newChildren[children.length] = entry;
+            children = newChildren;
+        }
+
+
+        public Entry[] getChildren() {
+            return children;
+        }
+
+
+        public Entry getChild(String name) {
+            for (int i = 0; i < children.length; i++) {
+                if (children[i].name.equals(name)) {
+                    return children[i];
+                }
+            }
+            return null;
+        }
+
+
+    }
+
+
+    // ------------------------------------------------ WARResource Inner Class
+
+
+    /**
+     * This specialized resource implementation avoids opening the IputStream
+     * to the WAR right away.
+     */
+    protected class WARResource extends Resource {
+        
+        
+        // -------------------------------------------------------- Constructor
+        
+        
+        public WARResource(ZipEntry entry) {
+            this.entry = entry;
+        }
+        
+        
+        // --------------------------------------------------- Member Variables
+        
+        
+        protected ZipEntry entry;
+        
+        
+        // ----------------------------------------------------- Public Methods
+        
+        
+        /**
+         * Content accessor.
+         * 
+         * @return InputStream
+         */
+        @Override
+        public InputStream streamContent()
+            throws IOException {
+            try {
+                if (binaryContent == null) {
+                    InputStream is = base.getInputStream(entry);
+                    inputStream = is;
+                    return is;
+                }
+            } catch (ZipException e) {
+                throw new IOException(e.getMessage(), e);
+            }
+            return super.streamContent();
+        }
+        
+        
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/jndi/Handler.java b/bundles/org.apache.tomcat/src/org/apache/naming/resources/jndi/Handler.java
new file mode 100644
index 0000000..fd56ccb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/jndi/Handler.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+package org.apache.naming.resources.jndi;
+
+import org.apache.naming.resources.DirContextURLStreamHandler;
+
+/**
+ * Stream handler to a JNDI directory context. For use when
+ * embedding Tomcat and the embedding application has already set its own
+ * {@link java.net.URLStreamHandlerFactory} and the Tomcat jndi handler needs to
+ * be registered via the java.protocol.handler.pkgs system property.
+ */
+public class Handler extends DirContextURLStreamHandler {
+    
+    public Handler() {
+        // NOOP
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/resources/package.html b/bundles/org.apache.tomcat/src/org/apache/naming/resources/package.html
new file mode 100644
index 0000000..c39e4b5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/resources/package.html
@@ -0,0 +1,31 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<body>
+
+<p>This package contains the resources directory context implementation.
+This includes :
+<ul>
+<li>A CacheDirContext which handles caching and acts as a proxy for the real 
+resources context</li>
+<li>A FileDirContext, an implementation of DirContext to access the file
+system</li>
+</ul>
+</p>
+
+<p></p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/File.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/File.java
new file mode 100644
index 0000000..950b049
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/File.java
@@ -0,0 +1,722 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+/* Import needed classes */
+import java.nio.ByteBuffer;
+/** File
+ *
+ * @author Mladen Turk
+ * @version $Id: File.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ */
+
+public class File {
+
+    /** Open the file for reading */
+    public static final int APR_FOPEN_READ       = 0x00001;
+    /** Open the file for writing */
+    public static final int APR_FOPEN_WRITE      = 0x00002;
+    /** Create the file if not there */
+    public static final int APR_FOPEN_CREATE     = 0x00004;
+    /** Append to the end of the file */
+    public static final int APR_FOPEN_APPEND     = 0x00008;
+    /** Open the file and truncate to 0 length */
+    public static final int APR_FOPEN_TRUNCATE   = 0x00010;
+    /** Open the file in binary mode */
+    public static final int APR_FOPEN_BINARY     = 0x00020;
+    /** Open should fail if APR_CREATE and file exists. */
+    public static final int APR_FOPEN_EXCL       = 0x00040;
+    /** Open the file for buffered I/O */
+    public static final int APR_FOPEN_BUFFERED   = 0x00080;
+    /** Delete the file after close */
+    public static final int APR_FOPEN_DELONCLOSE = 0x00100;
+    /** Platform dependent tag to open the file for
+     * use across multiple threads
+     */
+    public static final int APR_FOPEN_XTHREAD     = 0x00200;
+    /** Platform dependent support for higher level locked read/write
+     * access to support writes across process/machines
+     */
+    public static final int APR_FOPEN_SHARELOCK   = 0x00400;
+    /** Do not register a cleanup when the file is opened */
+    public static final int APR_FOPEN_NOCLEANUP   = 0x00800;
+    /** Advisory flag that this file should support
+     * apr_socket_sendfile operation
+     */
+    public static final int APR_FOPEN_SENDFILE_ENABLED = 0x01000;
+    /** Platform dependent flag to enable large file support;
+     * <br /><b>Warning :</b> The APR_LARGEFILE flag only has effect on some platforms
+     * where sizeof(apr_off_t) == 4.  Where implemented, it allows opening
+     * and writing to a file which exceeds the size which can be
+     * represented by apr_off_t (2 gigabytes).  When a file's size does
+     * exceed 2Gb, apr_file_info_get() will fail with an error on the
+     * descriptor, likewise apr_stat()/apr_lstat() will fail on the
+     * filename.  apr_dir_read() will fail with APR_INCOMPLETE on a
+     * directory entry for a large file depending on the particular
+     * APR_FINFO_* flags.  Generally, it is not recommended to use this
+     * flag.
+     */
+    public static final int APR_FOPEN_LARGEFILE      = 0x04000;
+
+    /** Set the file position */
+    public static final int APR_SET = 0;
+    /** Current */
+    public static final int APR_CUR = 1;
+    /** Go to end of file */
+    public static final int APR_END = 2;
+
+    /* flags for apr_file_attrs_set */
+
+    /** File is read-only */
+    public static final int APR_FILE_ATTR_READONLY   = 0x01;
+    /** File is executable */
+    public static final int APR_FILE_ATTR_EXECUTABLE = 0x02;
+    /** File is hidden */
+    public static final int APR_FILE_ATTR_HIDDEN     = 0x04;
+
+
+    /* File lock types/flags */
+
+    /** Shared lock. More than one process or thread can hold a shared lock
+     * at any given time. Essentially, this is a "read lock", preventing
+     * writers from establishing an exclusive lock.
+     */
+    public static final int APR_FLOCK_SHARED    = 1;
+
+    /** Exclusive lock. Only one process may hold an exclusive lock at any
+     * given time. This is analogous to a "write lock".
+     */
+    public static final int APR_FLOCK_EXCLUSIVE = 2;
+    /** mask to extract lock type */
+    public static final int APR_FLOCK_TYPEMASK  = 0x000F;
+    /** do not block while acquiring the file lock */
+    public static final int APR_FLOCK_NONBLOCK  = 0x0010;
+
+    /* apr_filetype_e values for the filetype member of the
+     * apr_file_info_t structure
+     * <br /><b>Warning :</b>: Not all of the filetypes below can be determined.
+     * For example, a given platform might not correctly report
+     * a socket descriptor as APR_SOCK if that type isn't
+     * well-identified on that platform.  In such cases where
+     * a filetype exists but cannot be described by the recognized
+     * flags below, the filetype will be APR_UNKFILE.  If the
+     * filetype member is not determined, the type will be APR_NOFILE.
+     */
+
+    /** no file type determined */
+    public static final int APR_NOFILE  = 0;
+    /** a regular file */
+    public static final int APR_REG     = 1;
+    /** a directory */
+    public static final int APR_DIR     = 2;
+    /** a character device */
+    public static final int APR_CHR     = 3;
+    /** a block device */
+    public static final int APR_BLK     = 4;
+    /** a FIFO / pipe */
+    public static final int APR_PIPE    = 5;
+    /** a symbolic link */
+    public static final int APR_LNK     = 6;
+    /** a [unix domain] socket */
+    public static final int APR_SOCK    = 7;
+    /** a file of some other unknown type */
+    public static final int APR_UNKFILE = 127;
+
+
+    /*
+     * apr_file_permissions File Permissions flags
+     */
+
+    public static final int APR_FPROT_USETID     = 0x8000; /** Set user id */
+    public static final int APR_FPROT_UREAD      = 0x0400; /** Read by user */
+    public static final int APR_FPROT_UWRITE     = 0x0200; /** Write by user */
+    public static final int APR_FPROT_UEXECUTE   = 0x0100; /** Execute by user */
+
+    public static final int APR_FPROT_GSETID     = 0x4000; /** Set group id */
+    public static final int APR_FPROT_GREAD      = 0x0040; /** Read by group */
+    public static final int APR_FPROT_GWRITE     = 0x0020; /** Write by group */
+    public static final int APR_FPROT_GEXECUTE   = 0x0010; /** Execute by group */
+
+    public static final int APR_FPROT_WSTICKY    = 0x2000; /** Sticky bit */
+    public static final int APR_FPROT_WREAD      = 0x0004; /** Read by others */
+    public static final int APR_FPROT_WWRITE     = 0x0002; /** Write by others */
+    public static final int APR_FPROT_WEXECUTE   = 0x0001; /** Execute by others */
+    public static final int APR_FPROT_OS_DEFAULT = 0x0FFF; /** use OS's default permissions */
+
+
+    public static final int APR_FINFO_LINK   = 0x00000001; /** Stat the link not the file itself if it is a link */
+    public static final int APR_FINFO_MTIME  = 0x00000010; /** Modification Time */
+    public static final int APR_FINFO_CTIME  = 0x00000020; /** Creation or inode-changed time */
+    public static final int APR_FINFO_ATIME  = 0x00000040; /** Access Time */
+    public static final int APR_FINFO_SIZE   = 0x00000100; /** Size of the file */
+    public static final int APR_FINFO_CSIZE  = 0x00000200; /** Storage size consumed by the file */
+    public static final int APR_FINFO_DEV    = 0x00001000; /** Device */
+    public static final int APR_FINFO_INODE  = 0x00002000; /** Inode */
+    public static final int APR_FINFO_NLINK  = 0x00004000; /** Number of links */
+    public static final int APR_FINFO_TYPE   = 0x00008000; /** Type */
+    public static final int APR_FINFO_USER   = 0x00010000; /** User */
+    public static final int APR_FINFO_GROUP  = 0x00020000; /** Group */
+    public static final int APR_FINFO_UPROT  = 0x00100000; /** User protection bits */
+    public static final int APR_FINFO_GPROT  = 0x00200000; /** Group protection bits */
+    public static final int APR_FINFO_WPROT  = 0x00400000; /** World protection bits */
+    public static final int APR_FINFO_ICASE  = 0x01000000; /** if dev is case insensitive */
+    public static final int APR_FINFO_NAME   = 0x02000000; /** ->name in proper case */
+
+    public static final int APR_FINFO_MIN    = 0x00008170; /** type, mtime, ctime, atime, size */
+    public static final int APR_FINFO_IDENT  = 0x00003000; /** dev and inode */
+    public static final int APR_FINFO_OWNER  = 0x00030000; /** user and group */
+    public static final int APR_FINFO_PROT   = 0x00700000; /**  all protections */
+    public static final int APR_FINFO_NORM   = 0x0073b170; /**  an atomic unix apr_stat() */
+    public static final int APR_FINFO_DIRENT = 0x02000000; /**  an atomic unix apr_dir_read() */
+
+
+
+    /**
+     * Open the specified file.
+     * @param fname The full path to the file (using / on all systems)
+     * @param flag Or'ed value of:
+     * <PRE>
+     * APR_FOPEN_READ              open for reading
+     * APR_FOPEN_WRITE             open for writing
+     * APR_FOPEN_CREATE            create the file if not there
+     * APR_FOPEN_APPEND            file ptr is set to end prior to all writes
+     * APR_FOPEN_TRUNCATE          set length to zero if file exists
+     * APR_FOPEN_BINARY            not a text file (This flag is ignored on
+     *                             UNIX because it has no meaning)
+     * APR_FOPEN_BUFFERED          buffer the data.  Default is non-buffered
+     * APR_FOPEN_EXCL              return error if APR_CREATE and file exists
+     * APR_FOPEN_DELONCLOSE        delete the file after closing.
+     * APR_FOPEN_XTHREAD           Platform dependent tag to open the file
+     *                             for use across multiple threads
+     * APR_FOPEN_SHARELOCK         Platform dependent support for higher
+     *                             level locked read/write access to support
+     *                             writes across process/machines
+     * APR_FOPEN_NOCLEANUP         Do not register a cleanup with the pool
+     *                             passed in on the <EM>pool</EM> argument (see below).
+     *                             The apr_os_file_t handle in apr_file_t will not
+     *                             be closed when the pool is destroyed.
+     * APR_FOPEN_SENDFILE_ENABLED  Open with appropriate platform semantics
+     *                             for sendfile operations.  Advisory only,
+     *                             apr_socket_sendfile does not check this flag.
+     * </PRE>
+     * @param perm Access permissions for file.
+     * @param pool The pool to use.
+     * If perm is APR_OS_DEFAULT and the file is being created,
+     * appropriate default permissions will be used.
+     * @return The opened file descriptor.
+     */
+    public static native long open(String fname, int flag, int perm, long pool)
+        throws Error;
+
+    /**
+     * Close the specified file.
+     * @param file The file descriptor to close.
+     */
+    public static native int close(long file);
+
+    /**
+     * Flush the file's buffer.
+     * @param thefile The file descriptor to flush
+     */
+    public static native int flush(long thefile);
+
+    /**
+     * Open a temporary file
+     * @param templ The template to use when creating a temp file.
+     * @param flags The flags to open the file with. If this is zero,
+     *              the file is opened with
+     *              APR_CREATE | APR_READ | APR_WRITE | APR_EXCL | APR_DELONCLOSE
+     * @param pool The pool to allocate the file out of.
+     * @return The apr file to use as a temporary file.
+     * 
+     * This function  generates  a unique temporary file name from template.
+     * The last six characters of template must be XXXXXX and these are replaced
+     * with a string that makes the filename unique. Since it will  be  modified,
+     * template must not be a string constant, but should be declared as a character
+     * array.
+     *
+     */
+    public static native long mktemp(String templ, int flags, long pool)
+        throws Error;
+
+    /**
+     * Delete the specified file.
+     * @param path The full path to the file (using / on all systems)
+     * @param pool The pool to use.
+     * If the file is open, it won't be removed until all
+     * instances are closed.
+     */
+    public static native int remove(String path, long pool);
+
+    /**
+     * Rename the specified file.
+     * <br /><b>Warning :</b> If a file exists at the new location, then it will be
+     * overwritten.  Moving files or directories across devices may not be
+     * possible.
+     * @param fromPath The full path to the original file (using / on all systems)
+     * @param toPath The full path to the new file (using / on all systems)
+     * @param pool The pool to use.
+     */
+    public static native int rename(String fromPath, String toPath, long pool);
+
+    /**
+     * Copy the specified file to another file.
+     * The new file does not need to exist, it will be created if required.
+     * <br /><b>Warning :</b> If the new file already exists, its contents will be overwritten.
+     * @param fromPath The full path to the original file (using / on all systems)
+     * @param toPath The full path to the new file (using / on all systems)
+     * @param perms Access permissions for the new file if it is created.
+     *     In place of the usual or'd combination of file permissions, the
+     *     value APR_FILE_SOURCE_PERMS may be given, in which case the source
+     *     file's permissions are copied.
+     * @param pool The pool to use.
+     */
+    public static native int copy(String fromPath, String toPath, int perms, long pool);
+
+    /**
+     * Append the specified file to another file.
+     * The new file does not need to exist, it will be created if required.
+     * @param fromPath The full path to the source file (use / on all systems)
+     * @param toPath The full path to the destination file (use / on all systems)
+     * @param perms Access permissions for the destination file if it is created.
+     *     In place of the usual or'd combination of file permissions, the
+     *     value APR_FILE_SOURCE_PERMS may be given, in which case the source
+     *     file's permissions are copied.
+     * @param pool The pool to use.
+     */
+    public static native int append(String fromPath, String toPath, int perms, long pool);
+
+    /**
+     * Write the string into the specified file.
+     * @param str The string to write. Must be NUL terminated!
+     * @param thefile The file descriptor to write to
+     */
+    public static native int puts(byte [] str, long thefile);
+
+    /**
+     * Move the read/write file offset to a specified byte within a file.
+     * @param thefile The file descriptor
+     * @param where How to move the pointer, one of:
+     * <PRE>
+     * APR_SET  --  set the offset to offset
+     * APR_CUR  --  add the offset to the current position
+     * APR_END  --  add the offset to the current file size
+     * </PRE>
+     * @param offset The offset to move the pointer to.
+     * @return Offset the pointer was actually moved to.
+     */
+    public static native long seek(long thefile, int where, long offset)
+        throws Error;
+
+    /**
+     * Write a character into the specified file.
+     * @param ch The character to write.
+     * @param thefile The file descriptor to write to
+     */
+    public static native int putc(byte ch, long thefile);
+
+    /**
+     * Put a character back onto a specified stream.
+     * @param ch The character to write.
+     * @param thefile The file descriptor to write to
+     */
+    public static native int ungetc(byte ch, long thefile);
+
+    /**
+     * Write data to the specified file.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, it
+     * will write as many as it can.  The third argument is modified to
+     * reflect the * number of bytes written.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The buffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write; (-1) for full array.
+     * @return The number of bytes written.
+     */
+    public static native int write(long thefile, byte[] buf, int offset, int nbytes);
+
+    /**
+     * Write data to the specified file.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, it
+     * will write as many as it can.  The third argument is modified to
+     * reflect the * number of bytes written.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The direct Byte buffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write
+     * @return The number of bytes written.
+     */
+    public static native int writeb(long thefile, ByteBuffer buf, int offset, int nbytes);
+
+    /**
+     * Write data to the specified file, ensuring that all of the data is
+     * written before returning.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, the
+     * process/thread will block until they can be written. Exceptional
+     * error such as "out of space" or "pipe closed" will terminate with
+     * an error.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  And if *bytes_written is less than nbytes, an
+     * accompanying error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The buffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write; (-1) for full array.
+     * @return The number of bytes written.
+     */
+    public static native int writeFull(long thefile, byte[] buf, int offset, int nbytes);
+
+    /**
+     * Write data to the specified file, ensuring that all of the data is
+     * written before returning.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, the
+     * process/thread will block until they can be written. Exceptional
+     * error such as "out of space" or "pipe closed" will terminate with
+     * an error.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  And if *bytes_written is less than nbytes, an
+     * accompanying error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The direct ByteBuffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write.
+     * @return The number of bytes written.
+     */
+    public static native int writeFullb(long thefile, ByteBuffer buf, int offset, int nbytes);
+
+    /**
+     * Write data from array of byte arrays to the specified file.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  APR_EINTR is never returned.
+     *
+     * apr_file_writev is available even if the underlying
+     * operating system doesn't provide writev().
+     * @param thefile The file descriptor to write to.
+     * @param vec The array from which to get the data to write to the file.
+     * @return The number of bytes written.
+     */
+    public static native int writev(long thefile, byte[][] vec);
+
+    /**
+     * Write data from array of byte arrays to the specified file,
+     * ensuring that all of the data is written before returning.
+     *
+     * writevFull is available even if the underlying
+     * operating system doesn't provide writev().
+     * @param thefile The file descriptor to write to.
+     * @param vec The array from which to get the data to write to the file.
+     * @return The number of bytes written.
+     */
+    public static native int writevFull(long thefile, byte[][] vec);
+
+    /**
+     * Read data from the specified file.
+     *
+     * apr_file_read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, all of the available data is read.  The third
+     * argument is modified to reflect the number of bytes read.  If a
+     * char was put back into the stream via ungetc, it will be the first
+     * character returned.
+     *
+     * It is not possible for both bytes to be read and an APR_EOF
+     * or other error to be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The buffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes read.
+     */
+    public static native int read(long thefile, byte[] buf,  int offset, int nbytes);
+
+    /**
+     * Read data from the specified file.
+     *
+     * apr_file_read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, all of the available data is read.  The third
+     * argument is modified to reflect the number of bytes read.  If a
+     * char was put back into the stream via ungetc, it will be the first
+     * character returned.
+     *
+     * It is not possible for both bytes to be read and an APR_EOF
+     * or other error to be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The direct Byte buffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public static native int readb(long thefile, ByteBuffer buf,  int offset, int nbytes);
+
+    /**
+     * Read data from the specified file, ensuring that the buffer is filled
+     * before returning.
+     *
+     * Read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, then the process/thread will block until it is
+     * available or EOF is reached.  If a char was put back into the
+     * stream via ungetc, it will be the first character returned.
+     *
+     * It is possible for both bytes to be read and an error to be
+     * returned.  And if *bytes_read is less than nbytes, an accompanying
+     * error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The buffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes read.
+     */
+    public static native int readFull(long thefile, byte[] buf,  int offset, int nbytes);
+
+    /**
+     * Read data from the specified file, ensuring that the buffer is filled
+     * before returning.
+     *
+     * Read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, then the process/thread will block until it is
+     * available or EOF is reached.  If a char was put back into the
+     * stream via ungetc, it will be the first character returned.
+     *
+     * It is possible for both bytes to be read and an error to be
+     * returned.  And if *bytes_read is less than nbytes, an accompanying
+     * error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The direct ByteBuffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public static native int readFullb(long thefile, ByteBuffer buf,  int offset, int nbytes);
+
+    /**
+     * Read a string from the specified file.
+     * The buffer will be NUL-terminated if any characters are stored.
+     * @param buf The buffer to store the string in.
+     * @param offset Start offset in buf
+     * @param thefile The file descriptor to read from
+     */
+    public static native int gets(byte[] buf,  int offset, long thefile);
+
+
+    /**
+     * Read a character from the specified file.
+     * @param thefile The file descriptor to read from
+     * @return The read character
+     */
+    public static native int getc(long thefile)
+        throws Error;
+
+    /**
+     * Are we at the end of the file
+     * @param fptr The apr file we are testing.
+     * @return Returns APR_EOF if we are at the end of file, APR_SUCCESS otherwise.
+     */
+    public static native int eof(long fptr);
+
+    /**
+     * return the file name of the current file.
+     * @param thefile The currently open file.
+     */
+    public static native String nameGet(long thefile);
+
+    /**
+     * Set the specified file's permission bits.
+     * <br /><b>Warning :</b> Some platforms may not be able to apply all of the
+     * available permission bits; APR_INCOMPLETE will be returned if some
+     * permissions are specified which could not be set.
+     * <br /><b>Warning :</b> Platforms which do not implement this feature will return
+     * APR_ENOTIMPL.
+     * @param fname The file (name) to apply the permissions to.
+     * @param perms The permission bits to apply to the file.
+     *
+     */
+    public static native int permsSet(String fname, int perms);
+
+    /**
+     * Set attributes of the specified file.
+     * This function should be used in preference to explicit manipulation
+     *      of the file permissions, because the operations to provide these
+     *      attributes are platform specific and may involve more than simply
+     *      setting permission bits.
+     * <br /><b>Warning :</b> Platforms which do not implement this feature will return
+     *      APR_ENOTIMPL.
+     * @param fname The full path to the file (using / on all systems)
+     * @param attributes Or'd combination of
+     * <PRE>
+     *            APR_FILE_ATTR_READONLY   - make the file readonly
+     *            APR_FILE_ATTR_EXECUTABLE - make the file executable
+     *            APR_FILE_ATTR_HIDDEN     - make the file hidden
+     * </PRE>
+     * @param mask Mask of valid bits in attributes.
+     * @param pool the pool to use.
+     */
+    public static native int  attrsSet(String fname, int attributes, int mask, long pool);
+
+    /**
+     * Set the mtime of the specified file.
+     * <br /><b>Warning :</b> Platforms which do not implement this feature will return
+     *      APR_ENOTIMPL.
+     * @param fname The full path to the file (using / on all systems)
+     * @param mtime The mtime to apply to the file in microseconds
+     * @param pool The pool to use.
+     */
+    public static native int  mtimeSet(String fname, long mtime, long pool);
+
+    /**
+     * Establish a lock on the specified, open file. The lock may be advisory
+     * or mandatory, at the discretion of the platform. The lock applies to
+     * the file as a whole, rather than a specific range. Locks are established
+     * on a per-thread/process basis; a second lock by the same thread will not
+     * block.
+     * @param thefile The file to lock.
+     * @param type The type of lock to establish on the file.
+     */
+    public static native int lock(long thefile, int type);
+
+    /**
+     * Remove any outstanding locks on the file.
+     * @param thefile The file to unlock.
+     */
+    public static native int unlock(long thefile);
+
+    /**
+     * Retrieve the flags that were passed into apr_file_open()
+     * when the file was opened.
+     * @param file The file to retrieve flags.
+     * @return the flags
+     */
+    public static native int flagsGet(long file);
+
+    /**
+     * Truncate the file's length to the specified offset
+     * @param fp The file to truncate
+     * @param offset The offset to truncate to.
+     */
+    public static native int trunc(long fp, long offset);
+
+    /**
+     * Create an anonymous pipe.
+     * @param io io[0] The file descriptors to use as input to the pipe.
+     *           io[1] The file descriptor to use as output from the pipe.
+     * @param pool The pool to operate on.
+     */
+    public static native int pipeCreate(long [] io, long pool);
+
+    /**
+     * Get the timeout value for a pipe or manipulate the blocking state.
+     * @param thepipe The pipe we are getting a timeout for.
+     * @return The current timeout value in microseconds.
+     */
+    public static native long pipeTimeoutGet(long thepipe)
+        throws Error;
+
+    /**
+     * Set the timeout value for a pipe or manipulate the blocking state.
+     * @param thepipe The pipe we are setting a timeout on.
+     * @param timeout The timeout value in microseconds.  Values < 0 mean wait
+     *        forever, 0 means do not wait at all.
+     */
+    public static native int pipeTimeoutSet(long thepipe, long timeout);
+
+    /**
+     * Duplicate the specified file descriptor.
+     * @param newFile The file to duplicate.
+     * newFile must point to a valid apr_file_t, or point to NULL.
+     * @param oldFile The file to duplicate.
+     * @param pool The pool to use for the new file.
+     * @return Duplicated file structure.
+     */
+    public static native long dup(long newFile, long oldFile, long pool)
+        throws Error;
+
+    /**
+     * Duplicate the specified file descriptor and close the original.
+     * @param newFile The old file that is to be closed and reused.
+     * newFile MUST point at a valid apr_file_t. It cannot be NULL.
+     * @param oldFile The file to duplicate.
+     * @param pool The pool to use for the new file.
+     * @return Status code.
+     */
+    public static native int dup2(long newFile, long oldFile, long pool);
+
+    /**
+     * Get the specified file's stats.  The file is specified by filename,
+     * instead of using a pre-opened file.
+     * @param finfo Where to store the information about the file, which is
+     * never touched if the call fails.
+     * @param fname The name of the file to stat.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param pool the pool to use to allocate the new file.
+     */
+    public static native int stat(FileInfo finfo, String fname, int wanted, long pool);
+
+    /**
+     * Get the specified file's stats.  The file is specified by filename,
+     * instead of using a pre-opened file.
+     * @param fname The name of the file to stat.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param pool the pool to use to allocate the new file.
+     * @return FileInfo object.
+     */
+    public static native FileInfo getStat(String fname, int wanted, long pool);
+
+    /**
+     * Get the specified file's stats.
+     * @param finfo Where to store the information about the file.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param thefile The file to get information about.
+     */
+    public static native int infoGet(FileInfo finfo, int wanted, long thefile);
+
+
+    /**
+     * Get the specified file's stats.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param thefile The file to get information about.
+     * @return FileInfo object.
+     */
+    public static native FileInfo getInfo(int wanted, long thefile);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Global.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Global.java
new file mode 100644
index 0000000..344afd4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Global.java
@@ -0,0 +1,96 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** Global
+ *
+ * @author Mladen Turk
+ * @version $Id: Global.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ */
+
+public class Global {
+
+    /**
+     * Create and initialize a mutex that can be used to synchronize both
+     * processes and threads. Note: There is considerable overhead in using
+     * this API if only cross-process or cross-thread mutual exclusion is
+     * required. See apr_proc_mutex.h and apr_thread_mutex.h for more
+     * specialized lock routines.
+     * <br /><b>Warning :</b> Check APR_HAS_foo_SERIALIZE defines to see if the platform supports
+     *          APR_LOCK_foo.  Only APR_LOCK_DEFAULT is portable.
+     * @param fname A file name to use if the lock mechanism requires one.  This
+     *        argument should always be provided.  The lock code itself will
+     *        determine if it should be used.
+     * @param mech The mechanism to use for the interprocess lock, if any; one of
+     * <PRE>
+     *            APR_LOCK_FCNTL
+     *            APR_LOCK_FLOCK
+     *            APR_LOCK_SYSVSEM
+     *            APR_LOCK_POSIXSEM
+     *            APR_LOCK_PROC_PTHREAD
+     *            APR_LOCK_DEFAULT     pick the default mechanism for the platform
+     * </PRE>
+     * @param pool the pool from which to allocate the mutex.
+     * @return Newly created mutex.
+     */
+    public static native long create(String fname, int mech, long pool)
+        throws Error;
+
+    /**
+     * Re-open a mutex in a child process.
+     * @param fname A file name to use if the mutex mechanism requires one.  This
+     *              argument should always be provided.  The mutex code itself will
+     *              determine if it should be used.  This filename should be the
+     *              same one that was passed to apr_proc_mutex_create().
+     * @param pool The pool to operate on.
+     * This function must be called to maintain portability, even
+     *         if the underlying lock mechanism does not require it.
+     * @return Newly opened mutex.
+     */
+    public static native long childInit(String fname, long pool)
+        throws Error;
+
+    /**
+     * Acquire the lock for the given mutex. If the mutex is already locked,
+     * the current thread will be put to sleep until the lock becomes available.
+     * @param mutex the mutex on which to acquire the lock.
+     */
+    public static native int lock(long mutex);
+
+    /**
+     * Attempt to acquire the lock for the given mutex. If the mutex has already
+     * been acquired, the call returns immediately with APR_EBUSY. Note: it
+     * is important that the APR_STATUS_IS_EBUSY(s) macro be used to determine
+     * if the return value was APR_EBUSY, for portability reasons.
+     * @param mutex the mutex on which to attempt the lock acquiring.
+     */
+    public static native int trylock(long mutex);
+
+    /**
+     * Release the lock for the given mutex.
+     * @param mutex the mutex from which to release the lock.
+     */
+    public static native int unlock(long mutex);
+
+    /**
+     * Destroy the mutex and free the memory associated with the lock.
+     * @param mutex the mutex to destroy.
+     */
+    public static native int destroy(long mutex);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/DomUtil.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/DomUtil.java
new file mode 100644
index 0000000..059353e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/DomUtil.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+/**
+ *  Few simple utils to read DOM
+ *
+ * @author Costin Manolache
+ */
+public class DomUtil {
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( DomUtil.class );
+
+    // -------------------- DOM utils --------------------
+
+    /** Get the trimmed text content of a node or null if there is no text
+     */
+    public static String getContent(Node n ) {
+        if( n==null ) return null;
+        Node n1=DomUtil.getChild(n, Node.TEXT_NODE);
+
+        if( n1==null ) return null;
+
+        String s1=n1.getNodeValue();
+        return s1.trim();
+    }
+
+    /** Get the first element child.
+     * @param parent lookup direct children
+     * @param name name of the element. If null return the first element.
+     */
+    public static Node getChild( Node parent, String name ) {
+        if( parent==null ) return null;
+        Node first=parent.getFirstChild();
+        if( first==null ) return null;
+
+        for (Node node = first; node != null;
+             node = node.getNextSibling()) {
+            //System.out.println("getNode: " + name + " " + node.getNodeName());
+            if( node.getNodeType()!=Node.ELEMENT_NODE)
+                continue;
+            if( name != null &&
+                name.equals( node.getNodeName() ) ) {
+                return node;
+            }
+            if( name == null ) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    public static String getAttribute(Node element, String attName ) {
+        NamedNodeMap attrs=element.getAttributes();
+        if( attrs==null ) return null;
+        Node attN=attrs.getNamedItem(attName);
+        if( attN==null ) return null;
+        return attN.getNodeValue();
+    }
+
+    public static void setAttribute(Node node, String attName, String val) {
+        NamedNodeMap attributes=node.getAttributes();
+        Node attNode=node.getOwnerDocument().createAttribute(attName);
+        attNode.setNodeValue( val );
+        attributes.setNamedItem(attNode);
+    }
+    
+    public static void removeAttribute( Node node, String attName ) {
+        NamedNodeMap attributes=node.getAttributes();
+        attributes.removeNamedItem(attName);                
+    }
+    
+    
+    /** Set or replace the text value 
+     */ 
+    public static void setText(Node node, String val) {
+        Node chld=DomUtil.getChild(node, Node.TEXT_NODE);
+        if( chld == null ) {
+            Node textN=node.getOwnerDocument().createTextNode(val);
+            node.appendChild(textN);
+            return;
+        }
+        // change the value
+        chld.setNodeValue(val);           
+    }
+
+    /** Find the first direct child with a given attribute.
+     * @param parent
+     * @param elemName name of the element, or null for any 
+     * @param attName attribute we're looking for
+     * @param attVal attribute value or null if we just want any
+     */ 
+    public static Node findChildWithAtt(Node parent, String elemName,
+                                        String attName, String attVal) {
+        
+        Node child=DomUtil.getChild(parent, Node.ELEMENT_NODE);
+        if( attVal== null ) {
+            while( child!= null &&
+                    ( elemName==null || elemName.equals( child.getNodeName())) && 
+                    DomUtil.getAttribute(child, attName) != null ) {
+                child=getNext(child, elemName, Node.ELEMENT_NODE );
+            }
+        } else {
+            while( child!= null && 
+                    ( elemName==null || elemName.equals( child.getNodeName())) && 
+                    ! attVal.equals( DomUtil.getAttribute(child, attName)) ) {
+                child=getNext(child, elemName, Node.ELEMENT_NODE );
+            }
+        }
+        return child;        
+    }    
+    
+
+    /** Get the first child's content ( ie it's included TEXT node ).
+     */
+    public static String getChildContent( Node parent, String name ) {
+        Node first=parent.getFirstChild();
+        if( first==null ) return null;
+        for (Node node = first; node != null;
+             node = node.getNextSibling()) {
+            //System.out.println("getNode: " + name + " " + node.getNodeName());
+            if( name.equals( node.getNodeName() ) ) {
+                return getContent( node );
+            }
+        }
+        return null;
+    }
+
+    /** Get the first direct child with a given type
+     */
+    public static Node getChild( Node parent, int type ) {
+        Node n=parent.getFirstChild();
+        while( n!=null && type != n.getNodeType() ) {
+            n=n.getNextSibling();
+        }
+        if( n==null ) return null;
+        return n;
+    }
+
+    /** Get the next sibling with the same name and type
+     */
+    public static Node getNext( Node current ) {
+        String name=current.getNodeName();
+        int type=current.getNodeType();
+        return getNext( current, name, type);
+    }
+
+    /** Return the next sibling with a given name and type
+     */ 
+    public static Node getNext( Node current, String name, int type) {
+        Node first=current.getNextSibling();
+        if( first==null ) return null;
+
+        for (Node node = first; node != null;
+             node = node.getNextSibling()) {
+            
+            if( type >= 0 && node.getNodeType() != type ) continue;
+            //System.out.println("getNode: " + name + " " + node.getNodeName());
+            if( name==null )
+                return node;
+            if( name.equals( node.getNodeName() ) ) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    public static class NullResolver implements EntityResolver {
+        public InputSource resolveEntity (String publicId,
+                                                   String systemId)
+            throws SAXException, IOException
+        {
+            if( log.isTraceEnabled())
+                log.trace("ResolveEntity: " + publicId + " " + systemId);
+            return new InputSource(new StringReader(""));
+        }
+    }
+
+    public static void setAttributes( Object o, Node parent)
+    {
+        NamedNodeMap attrs=parent.getAttributes();
+        if( attrs==null ) return;
+
+        for (int i=0; i<attrs.getLength(); i++ ) {
+            Node n=attrs.item(i);
+            String name=n.getNodeName();
+            String value=n.getNodeValue();
+
+            if( log.isTraceEnabled() )
+                log.trace("Attribute " + parent.getNodeName() + " " +
+                            name + "=" + value);
+            try {
+                IntrospectionUtils.setProperty(o, name, value);
+            } catch( Exception ex ) {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+    /** Read XML as DOM.
+     */
+    public static Document readXml(InputStream is)
+        throws SAXException, IOException, ParserConfigurationException
+    {
+        DocumentBuilderFactory dbf =
+            DocumentBuilderFactory.newInstance();
+
+        dbf.setValidating(false);
+        dbf.setIgnoringComments(false);
+        dbf.setIgnoringElementContentWhitespace(true);
+        //dbf.setCoalescing(true);
+        //dbf.setExpandEntityReferences(true);
+
+        DocumentBuilder db = null;
+        db = dbf.newDocumentBuilder();
+        db.setEntityResolver( new NullResolver() );
+
+        // db.setErrorHandler( new MyErrorHandler());
+
+        Document doc = db.parse(is);
+        return doc;
+    }
+
+    public static void writeXml( Node n, OutputStream os )
+            throws TransformerException
+    {
+        TransformerFactory tf=TransformerFactory.newInstance();
+        //identity
+        Transformer t=tf.newTransformer();
+        t.setOutputProperty(OutputKeys.INDENT, "yes");
+        t.transform(new DOMSource( n ), new StreamResult( os ));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/ExceptionUtils.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/ExceptionUtils.java
new file mode 100644
index 0000000..7546141
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/ExceptionUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util;
+
+/**
+ * Utilities for handling Throwables and Exceptions.
+ */
+public class ExceptionUtils {
+    
+    /**
+     * Checks whether the supplied Throwable is one that needs to be 
+     * rethrown and swallows all others.
+     * @param t the Throwable to check
+     */
+    public static void handleThrowable(Throwable t) {
+        if (t instanceof ThreadDeath) {
+            throw (ThreadDeath) t;
+        }
+        if (t instanceof VirtualMachineError) {
+            throw (VirtualMachineError) t;
+        }
+        // All other instances of Throwable will be silently swallowed
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/IntrospectionUtils.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/IntrospectionUtils.java
new file mode 100644
index 0000000..1c4b313
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/IntrospectionUtils.java
@@ -0,0 +1,909 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+// Depends: JDK1.1
+
+/**
+ * Utils for introspection and reflection
+ */
+public final class IntrospectionUtils {
+
+    
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( IntrospectionUtils.class );
+    
+    /**
+     * Call execute() - any ant-like task should work
+     */
+    public static void execute(Object proxy, String method) throws Exception {
+        Method executeM = null;
+        Class<?> c = proxy.getClass();
+        Class<?> params[] = new Class[0];
+        // params[0]=args.getClass();
+        executeM = findMethod(c, method, params);
+        if (executeM == null) {
+            throw new RuntimeException("No execute in " + proxy.getClass());
+        }
+        executeM.invoke(proxy, (Object[]) null);//new Object[] { args });
+    }
+
+    /**
+     * Call void setAttribute( String ,Object )
+     */
+    public static void setAttribute(Object proxy, String n, Object v)
+            throws Exception {
+        if (proxy instanceof AttributeHolder) {
+            ((AttributeHolder) proxy).setAttribute(n, v);
+            return;
+        }
+
+        Method executeM = null;
+        Class<?> c = proxy.getClass();
+        Class<?> params[] = new Class[2];
+        params[0] = String.class;
+        params[1] = Object.class;
+        executeM = findMethod(c, "setAttribute", params);
+        if (executeM == null) {
+            if (log.isDebugEnabled())
+                log.debug("No setAttribute in " + proxy.getClass());
+            return;
+        }
+        if (log.isDebugEnabled())
+            log.debug("Setting " + n + "=" + v + "  in " + proxy);
+        executeM.invoke(proxy, new Object[] { n, v });
+        return;
+    }
+
+    /**
+     * Call void getAttribute( String )
+     */
+    public static Object getAttribute(Object proxy, String n) throws Exception {
+        Method executeM = null;
+        Class<?> c = proxy.getClass();
+        Class<?> params[] = new Class[1];
+        params[0] = String.class;
+        executeM = findMethod(c, "getAttribute", params);
+        if (executeM == null) {
+            if (log.isDebugEnabled())
+                log.debug("No getAttribute in " + proxy.getClass());
+            return null;
+        }
+        return executeM.invoke(proxy, new Object[] { n });
+    }
+
+    /**
+     * Construct a URLClassLoader. Will compile and work in JDK1.1 too.
+     */
+    public static ClassLoader getURLClassLoader(URL urls[], ClassLoader parent) {
+        try {
+            Class<?> urlCL = Class.forName("java.net.URLClassLoader");
+            Class<?> paramT[] = new Class[2];
+            paramT[0] = urls.getClass();
+            paramT[1] = ClassLoader.class;
+            Method m = findMethod(urlCL, "newInstance", paramT);
+            if (m == null)
+                return null;
+
+            ClassLoader cl = (ClassLoader) m.invoke(urlCL, new Object[] { urls,
+                    parent });
+            return cl;
+        } catch (ClassNotFoundException ex) {
+            // jdk1.1
+            return null;
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
+    public static String guessInstall(String installSysProp,
+            String homeSysProp, String jarName) {
+        return guessInstall(installSysProp, homeSysProp, jarName, null);
+    }
+
+    /**
+     * Guess a product install/home by analyzing the class path. It works for
+     * product using the pattern: lib/executable.jar or if executable.jar is
+     * included in classpath by a shell script. ( java -jar also works )
+     * 
+     * Insures both "install" and "home" System properties are set. If either or
+     * both System properties are unset, "install" and "home" will be set to the
+     * same value. This value will be the other System property that is set, or
+     * the guessed value if neither is set.
+     */
+    public static String guessInstall(String installSysProp,
+            String homeSysProp, String jarName, String classFile) {
+        String install = null;
+        String home = null;
+
+        if (installSysProp != null)
+            install = System.getProperty(installSysProp);
+
+        if (homeSysProp != null)
+            home = System.getProperty(homeSysProp);
+
+        if (install != null) {
+            if (home == null)
+                System.getProperties().put(homeSysProp, install);
+            return install;
+        }
+
+        // Find the directory where jarName.jar is located
+
+        String cpath = System.getProperty("java.class.path");
+        String pathSep = System.getProperty("path.separator");
+        StringTokenizer st = new StringTokenizer(cpath, pathSep);
+        while (st.hasMoreTokens()) {
+            String path = st.nextToken();
+            // log( "path " + path );
+            if (path.endsWith(jarName)) {
+                home = path.substring(0, path.length() - jarName.length());
+                try {
+                    if ("".equals(home)) {
+                        home = new File("./").getCanonicalPath();
+                    } else if (home.endsWith(File.separator)) {
+                        home = home.substring(0, home.length() - 1);
+                    }
+                    File f = new File(home);
+                    String parentDir = f.getParent();
+                    if (parentDir == null)
+                        parentDir = home; // unix style
+                    File f1 = new File(parentDir);
+                    install = f1.getCanonicalPath();
+                    if (installSysProp != null)
+                        System.getProperties().put(installSysProp, install);
+                    if (home == null && homeSysProp != null)
+                        System.getProperties().put(homeSysProp, install);
+                    return install;
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                }
+            } else {
+                String fname = path + (path.endsWith("/") ? "" : "/")
+                        + classFile;
+                if (new File(fname).exists()) {
+                    try {
+                        File f = new File(path);
+                        String parentDir = f.getParent();
+                        if (parentDir == null)
+                            parentDir = path; // unix style
+                        File f1 = new File(parentDir);
+                        install = f1.getCanonicalPath();
+                        if (installSysProp != null)
+                            System.getProperties().put(installSysProp, install);
+                        if (home == null && homeSysProp != null)
+                            System.getProperties().put(homeSysProp, install);
+                        return install;
+                    } catch (Exception ex) {
+                        ex.printStackTrace();
+                    }
+                }
+            }
+        }
+
+        // if install directory can't be found, use home as the default
+        if (home != null) {
+            System.getProperties().put(installSysProp, home);
+            return home;
+        }
+
+        return null;
+    }
+
+    /**
+     * Debug method, display the classpath
+     */
+    public static void displayClassPath(String msg, URL[] cp) {
+        if (log.isDebugEnabled()) {
+            log.debug(msg);
+            for (int i = 0; i < cp.length; i++) {
+                log.debug(cp[i].getFile());
+            }
+        }
+    }
+
+    public static final String PATH_SEPARATOR = System.getProperty("path.separator");
+
+    /**
+     * Adds classpath entries from a vector of URL's to the "tc_path_add" System
+     * property. This System property lists the classpath entries common to web
+     * applications. This System property is currently used by Jasper when its
+     * JSP servlet compiles the Java file for a JSP.
+     */
+    public static String classPathAdd(URL urls[], String cp) {
+        if (urls == null)
+            return cp;
+
+        for (int i = 0; i < urls.length; i++) {
+            if (cp != null)
+                cp += PATH_SEPARATOR + urls[i].getFile();
+            else
+                cp = urls[i].getFile();
+        }
+        return cp;
+    }
+
+    /**
+     * Find a method with the right name If found, call the method ( if param is
+     * int or boolean we'll convert value to the right type before) - that means
+     * you can have setDebug(1).
+     */
+    public static boolean setProperty(Object o, String name, String value) {
+        return setProperty(o,name,value,true);
+    }
+    public static boolean setProperty(Object o, String name, String value,
+            boolean invokeSetProperty) {
+        if (log.isDebugEnabled())
+            log.debug("IntrospectionUtils: setProperty(" +
+                    o.getClass() + " " + name + "=" + value + ")");
+
+        String setter = "set" + capitalize(name);
+
+        try {
+            Method methods[] = findMethods(o.getClass());
+            Method setPropertyMethodVoid = null;
+            Method setPropertyMethodBool = null;
+
+            // First, the ideal case - a setFoo( String ) method
+            for (int i = 0; i < methods.length; i++) {
+                Class<?> paramT[] = methods[i].getParameterTypes();
+                if (setter.equals(methods[i].getName()) && paramT.length == 1
+                        && "java.lang.String".equals(paramT[0].getName())) {
+
+                    methods[i].invoke(o, new Object[] { value });
+                    return true;
+                }
+            }
+
+            // Try a setFoo ( int ) or ( boolean )
+            for (int i = 0; i < methods.length; i++) {
+                boolean ok = true;
+                if (setter.equals(methods[i].getName())
+                        && methods[i].getParameterTypes().length == 1) {
+
+                    // match - find the type and invoke it
+                    Class<?> paramType = methods[i].getParameterTypes()[0];
+                    Object params[] = new Object[1];
+
+                    // Try a setFoo ( int )
+                    if ("java.lang.Integer".equals(paramType.getName())
+                            || "int".equals(paramType.getName())) {
+                        try {
+                            params[0] = new Integer(value);
+                        } catch (NumberFormatException ex) {
+                            ok = false;
+                        }
+                    // Try a setFoo ( long )
+                    }else if ("java.lang.Long".equals(paramType.getName())
+                                || "long".equals(paramType.getName())) {
+                            try {
+                                params[0] = new Long(value);
+                            } catch (NumberFormatException ex) {
+                                ok = false;
+                            }
+
+                        // Try a setFoo ( boolean )
+                    } else if ("java.lang.Boolean".equals(paramType.getName())
+                            || "boolean".equals(paramType.getName())) {
+                        params[0] = Boolean.valueOf(value);
+
+                        // Try a setFoo ( InetAddress )
+                    } else if ("java.net.InetAddress".equals(paramType
+                            .getName())) {
+                        try {
+                            params[0] = InetAddress.getByName(value);
+                        } catch (UnknownHostException exc) {
+                            if (log.isDebugEnabled())
+                                log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
+                            ok = false;
+                        }
+
+                        // Unknown type
+                    } else {
+                        if (log.isDebugEnabled())
+                            log.debug("IntrospectionUtils: Unknown type " +
+                                    paramType.getName());
+                    }
+
+                    if (ok) {
+                        methods[i].invoke(o, params);
+                        return true;
+                    }
+                }
+
+                // save "setProperty" for later
+                if ("setProperty".equals(methods[i].getName())) {
+                    if (methods[i].getReturnType()==Boolean.TYPE){
+                        setPropertyMethodBool = methods[i];
+                    }else {
+                        setPropertyMethodVoid = methods[i];    
+                    }
+                    
+                }
+            }
+
+            // Ok, no setXXX found, try a setProperty("name", "value")
+            if (invokeSetProperty && (setPropertyMethodBool != null ||
+                    setPropertyMethodVoid != null)) {
+                Object params[] = new Object[2];
+                params[0] = name;
+                params[1] = value;
+                if (setPropertyMethodBool != null) {
+                    try {
+                        return (Boolean) setPropertyMethodBool.invoke(o, params);
+                    }catch (IllegalArgumentException biae) {
+                        //the boolean method had the wrong
+                        //parameter types. lets try the other
+                        if (setPropertyMethodVoid!=null) {
+                            setPropertyMethodVoid.invoke(o, params);
+                            return true;
+                        }else {
+                            throw biae;
+                        }
+                    }
+                } else {
+                    setPropertyMethodVoid.invoke(o, params);
+                    return true;
+                }
+            }
+
+        } catch (IllegalArgumentException ex2) {
+            log.warn("IAE " + o + " " + name + " " + value, ex2);
+        } catch (SecurityException ex1) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: SecurityException for " +
+                        o.getClass() + " " + name + "=" + value + ")", ex1);
+        } catch (IllegalAccessException iae) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: IllegalAccessException for " +
+                        o.getClass() + " " + name + "=" + value + ")", iae);
+        } catch (InvocationTargetException ie) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: InvocationTargetException for " +
+                        o.getClass() + " " + name + "=" + value + ")", ie);
+        }
+        return false;
+    }
+
+    public static Object getProperty(Object o, String name) {
+        String getter = "get" + capitalize(name);
+        String isGetter = "is" + capitalize(name);
+
+        try {
+            Method methods[] = findMethods(o.getClass());
+            Method getPropertyMethod = null;
+
+            // First, the ideal case - a getFoo() method
+            for (int i = 0; i < methods.length; i++) {
+                Class<?> paramT[] = methods[i].getParameterTypes();
+                if (getter.equals(methods[i].getName()) && paramT.length == 0) {
+                    return methods[i].invoke(o, (Object[]) null);
+                }
+                if (isGetter.equals(methods[i].getName()) && paramT.length == 0) {
+                    return methods[i].invoke(o, (Object[]) null);
+                }
+
+                if ("getProperty".equals(methods[i].getName())) {
+                    getPropertyMethod = methods[i];
+                }
+            }
+
+            // Ok, no setXXX found, try a getProperty("name")
+            if (getPropertyMethod != null) {
+                Object params[] = new Object[1];
+                params[0] = name;
+                return getPropertyMethod.invoke(o, params);
+            }
+
+        } catch (IllegalArgumentException ex2) {
+            log.warn("IAE " + o + " " + name, ex2);
+        } catch (SecurityException ex1) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: SecurityException for " +
+                        o.getClass() + " " + name + ")", ex1);
+        } catch (IllegalAccessException iae) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: IllegalAccessException for " +
+                        o.getClass() + " " + name + ")", iae);
+        } catch (InvocationTargetException ie) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: InvocationTargetException for " +
+                        o.getClass() + " " + name + ")");
+        }
+        return null;
+    }
+
+    /** 
+     */
+    public static void setProperty(Object o, String name) {
+        String setter = "set" + capitalize(name);
+        try {
+            Method methods[] = findMethods(o.getClass());
+            // find setFoo() method
+            for (int i = 0; i < methods.length; i++) {
+                Class<?> paramT[] = methods[i].getParameterTypes();
+                if (setter.equals(methods[i].getName()) && paramT.length == 0) {
+                    methods[i].invoke(o, new Object[] {});
+                    return;
+                }
+            }
+        } catch (Exception ex1) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: Exception for " +
+                        o.getClass() + " " + name, ex1);
+        }
+    }
+
+    /**
+     * Replace ${NAME} with the property value
+     */
+    public static String replaceProperties(String value,
+            Hashtable<Object,Object> staticProp, PropertySource dynamicProp[]) {
+        if (value.indexOf("$") < 0) {
+            return value;
+        }
+        StringBuilder sb = new StringBuilder();
+        int prev = 0;
+        // assert value!=nil
+        int pos;
+        while ((pos = value.indexOf("$", prev)) >= 0) {
+            if (pos > 0) {
+                sb.append(value.substring(prev, pos));
+            }
+            if (pos == (value.length() - 1)) {
+                sb.append('$');
+                prev = pos + 1;
+            } else if (value.charAt(pos + 1) != '{') {
+                sb.append('$');
+                prev = pos + 1; // XXX
+            } else {
+                int endName = value.indexOf('}', pos);
+                if (endName < 0) {
+                    sb.append(value.substring(pos));
+                    prev = value.length();
+                    continue;
+                }
+                String n = value.substring(pos + 2, endName);
+                String v = null;
+                if (staticProp != null) {
+                    v = (String) staticProp.get(n);
+                }
+                if (v == null && dynamicProp != null) {
+                    for (int i = 0; i < dynamicProp.length; i++) {
+                        v = dynamicProp[i].getProperty(n);
+                        if (v != null) {
+                            break;
+                        }
+                    }
+                }
+                if (v == null)
+                    v = "${" + n + "}";
+
+                sb.append(v);
+                prev = endName + 1;
+            }
+        }
+        if (prev < value.length())
+            sb.append(value.substring(prev));
+        return sb.toString();
+    }
+
+    /**
+     * Reverse of Introspector.decapitalize
+     */
+    public static String capitalize(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+        char chars[] = name.toCharArray();
+        chars[0] = Character.toUpperCase(chars[0]);
+        return new String(chars);
+    }
+
+    public static String unCapitalize(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+        char chars[] = name.toCharArray();
+        chars[0] = Character.toLowerCase(chars[0]);
+        return new String(chars);
+    }
+
+    // -------------------- Class path tools --------------------
+
+    /**
+     * Add all the jar files in a dir to the classpath, represented as a Vector
+     * of URLs.
+     */
+    public static void addToClassPath(Vector<URL> cpV, String dir) {
+        try {
+            String cpComp[] = getFilesByExt(dir, ".jar");
+            if (cpComp != null) {
+                int jarCount = cpComp.length;
+                for (int i = 0; i < jarCount; i++) {
+                    URL url = getURL(dir, cpComp[i]);
+                    if (url != null)
+                        cpV.addElement(url);
+                }
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    public static void addToolsJar(Vector<URL> v) {
+        try {
+            // Add tools.jar in any case
+            File f = new File(System.getProperty("java.home")
+                    + "/../lib/tools.jar");
+
+            if (!f.exists()) {
+                // On some systems java.home gets set to the root of jdk.
+                // That's a bug, but we can work around and be nice.
+                f = new File(System.getProperty("java.home") + "/lib/tools.jar");
+                if (f.exists()) {
+                    if (log.isDebugEnabled())
+                        log.debug("Detected strange java.home value "
+                            + System.getProperty("java.home")
+                            + ", it should point to jre");
+                }
+            }
+            URL url = new URL("file", "", f.getAbsolutePath());
+
+            v.addElement(url);
+        } catch (MalformedURLException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    /**
+     * Return all files with a given extension in a dir
+     */
+    public static String[] getFilesByExt(String ld, String ext) {
+        File dir = new File(ld);
+        String[] names = null;
+        final String lext = ext;
+        if (dir.isDirectory()) {
+            names = dir.list(new FilenameFilter() {
+                public boolean accept(File d, String name) {
+                    if (name.endsWith(lext)) {
+                        return true;
+                    }
+                    return false;
+                }
+            });
+        }
+        return names;
+    }
+
+    /**
+     * Construct a file url from a file, using a base dir
+     */
+    public static URL getURL(String base, String file) {
+        try {
+            File baseF = new File(base);
+            File f = new File(baseF, file);
+            String path = f.getCanonicalPath();
+            if (f.isDirectory()) {
+                path += "/";
+            }
+            if (!f.exists())
+                return null;
+            return new URL("file", "", path);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Add elements from the classpath <i>cp </i> to a Vector <i>jars </i> as
+     * file URLs (We use Vector for JDK 1.1 compat).
+     * <p>
+     * 
+     * @param jars The jar list
+     * @param cp a String classpath of directory or jar file elements
+     *   separated by path.separator delimiters.
+     * @throws IOException If an I/O error occurs
+     * @throws MalformedURLException Doh ;)
+     */
+    public static void addJarsFromClassPath(Vector<URL> jars, String cp)
+            throws IOException, MalformedURLException {
+        String sep = System.getProperty("path.separator");
+        StringTokenizer st;
+        if (cp != null) {
+            st = new StringTokenizer(cp, sep);
+            while (st.hasMoreTokens()) {
+                File f = new File(st.nextToken());
+                String path = f.getCanonicalPath();
+                if (f.isDirectory()) {
+                    path += "/";
+                }
+                URL url = new URL("file", "", path);
+                if (!jars.contains(url)) {
+                    jars.addElement(url);
+                }
+            }
+        }
+    }
+
+    /**
+     * Return a URL[] that can be used to construct a class loader
+     */
+    public static URL[] getClassPath(Vector<URL> v) {
+        URL[] urls = new URL[v.size()];
+        for (int i = 0; i < v.size(); i++) {
+            urls[i] = v.elementAt(i);
+        }
+        return urls;
+    }
+
+    /**
+     * Construct a URL classpath from files in a directory, a cpath property,
+     * and tools.jar.
+     */
+    public static URL[] getClassPath(String dir, String cpath,
+            String cpathProp, boolean addTools) throws IOException,
+            MalformedURLException {
+        Vector<URL> jarsV = new Vector<URL>();
+        if (dir != null) {
+            // Add dir/classes first, if it exists
+            URL url = getURL(dir, "classes");
+            if (url != null)
+                jarsV.addElement(url);
+            addToClassPath(jarsV, dir);
+        }
+
+        if (cpath != null)
+            addJarsFromClassPath(jarsV, cpath);
+
+        if (cpathProp != null) {
+            String cpath1 = System.getProperty(cpathProp);
+            addJarsFromClassPath(jarsV, cpath1);
+        }
+
+        if (addTools)
+            addToolsJar(jarsV);
+
+        return getClassPath(jarsV);
+    }
+
+    // -------------------- other utils --------------------
+    public static void clear() {
+        objectMethods.clear();
+    }
+    
+    static Hashtable<Class<?>,Method[]> objectMethods =
+        new Hashtable<Class<?>,Method[]>();
+
+    public static Method[] findMethods(Class<?> c) {
+        Method methods[] = objectMethods.get(c);
+        if (methods != null)
+            return methods;
+
+        methods = c.getMethods();
+        objectMethods.put(c, methods);
+        return methods;
+    }
+
+    public static Method findMethod(Class<?> c, String name,
+            Class<?> params[]) {
+        Method methods[] = findMethods(c);
+        if (methods == null)
+            return null;
+        for (int i = 0; i < methods.length; i++) {
+            if (methods[i].getName().equals(name)) {
+                Class<?> methodParams[] = methods[i].getParameterTypes();
+                if (methodParams == null)
+                    if (params == null || params.length == 0)
+                        return methods[i];
+                if (params == null)
+                    if (methodParams == null || methodParams.length == 0)
+                        return methods[i];
+                if (params.length != methodParams.length)
+                    continue;
+                boolean found = true;
+                for (int j = 0; j < params.length; j++) {
+                    if (params[j] != methodParams[j]) {
+                        found = false;
+                        break;
+                    }
+                }
+                if (found)
+                    return methods[i];
+            }
+        }
+        return null;
+    }
+
+    /** Test if the object implements a particular
+     *  method
+     */
+    public static boolean hasHook(Object obj, String methodN) {
+        try {
+            Method myMethods[] = findMethods(obj.getClass());
+            for (int i = 0; i < myMethods.length; i++) {
+                if (methodN.equals(myMethods[i].getName())) {
+                    // check if it's overridden
+                    Class<?> declaring = myMethods[i].getDeclaringClass();
+                    Class<?> parentOfDeclaring = declaring.getSuperclass();
+                    // this works only if the base class doesn't extend
+                    // another class.
+
+                    // if the method is declared in a top level class
+                    // like BaseInterceptor parent is Object, otherwise
+                    // parent is BaseInterceptor or an intermediate class
+                    if (!"java.lang.Object".equals(parentOfDeclaring.getName())) {
+                        return true;
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return false;
+    }
+
+    public static void callMain(Class<?> c, String args[]) throws Exception {
+        Class<?> p[] = new Class[1];
+        p[0] = args.getClass();
+        Method m = c.getMethod("main", p);
+        m.invoke(c, new Object[] { args });
+    }
+
+    public static Object callMethod1(Object target, String methodN,
+            Object param1, String typeParam1, ClassLoader cl) throws Exception {
+        if (target == null || param1 == null) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: Assert: Illegal params " +
+                        target + " " + param1);
+        }
+        if (log.isDebugEnabled())
+            log.debug("IntrospectionUtils: callMethod1 " +
+                    target.getClass().getName() + " " +
+                    param1.getClass().getName() + " " + typeParam1);
+
+        Class<?> params[] = new Class[1];
+        if (typeParam1 == null)
+            params[0] = param1.getClass();
+        else
+            params[0] = cl.loadClass(typeParam1);
+        Method m = findMethod(target.getClass(), methodN, params);
+        if (m == null)
+            throw new NoSuchMethodException(target.getClass().getName() + " "
+                    + methodN);
+        return m.invoke(target, new Object[] { param1 });
+    }
+
+    public static Object callMethod0(Object target, String methodN)
+            throws Exception {
+        if (target == null) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: Assert: Illegal params " +
+                        target);
+            return null;
+        }
+        if (log.isDebugEnabled())
+            log.debug("IntrospectionUtils: callMethod0 " +
+                    target.getClass().getName() + "." + methodN);
+
+        Class<?> params[] = new Class[0];
+        Method m = findMethod(target.getClass(), methodN, params);
+        if (m == null)
+            throw new NoSuchMethodException(target.getClass().getName() + " "
+                    + methodN);
+        return m.invoke(target, emptyArray);
+    }
+
+    static Object[] emptyArray = new Object[] {};
+
+    public static Object callMethodN(Object target, String methodN,
+            Object params[], Class<?> typeParams[]) throws Exception {
+        Method m = null;
+        m = findMethod(target.getClass(), methodN, typeParams);
+        if (m == null) {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: Can't find method " + methodN +
+                        " in " + target + " CLASS " + target.getClass());
+            return null;
+        }
+        Object o = m.invoke(target, params);
+
+        if (log.isDebugEnabled()) {
+            // debug
+            StringBuilder sb = new StringBuilder();
+            sb.append("" + target.getClass().getName() + "." + methodN + "( ");
+            for (int i = 0; i < params.length; i++) {
+                if (i > 0)
+                    sb.append(", ");
+                sb.append(params[i]);
+            }
+            sb.append(")");
+            log.debug("IntrospectionUtils:" + sb.toString());
+        }
+        return o;
+    }
+
+    public static Object convert(String object, Class<?> paramType) {
+        Object result = null;
+        if ("java.lang.String".equals(paramType.getName())) {
+            result = object;
+        } else if ("java.lang.Integer".equals(paramType.getName())
+                || "int".equals(paramType.getName())) {
+            try {
+                result = new Integer(object);
+            } catch (NumberFormatException ex) {
+            }
+            // Try a setFoo ( boolean )
+        } else if ("java.lang.Boolean".equals(paramType.getName())
+                || "boolean".equals(paramType.getName())) {
+            result = Boolean.valueOf(object);
+
+            // Try a setFoo ( InetAddress )
+        } else if ("java.net.InetAddress".equals(paramType
+                .getName())) {
+            try {
+                result = InetAddress.getByName(object);
+            } catch (UnknownHostException exc) {
+                if (log.isDebugEnabled())
+                    log.debug("IntrospectionUtils: Unable to resolve host name:" +
+                            object);
+            }
+
+            // Unknown type
+        } else {
+            if (log.isDebugEnabled())
+                log.debug("IntrospectionUtils: Unknown type " +
+                        paramType.getName());
+        }
+        if (result == null) {
+            throw new IllegalArgumentException("Can't convert argument: " + object);
+        }
+        return result;
+    }
+    
+    // -------------------- Get property --------------------
+    // This provides a layer of abstraction
+
+    public static interface PropertySource {
+
+        public String getProperty(String key);
+
+    }
+
+    public static interface AttributeHolder {
+
+        public void setAttribute(String key, Object o);
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/MutableInteger.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/MutableInteger.java
new file mode 100644
index 0000000..17bd96a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/MutableInteger.java
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util;
+
+public class MutableInteger {
+    protected int value = 0;
+    public MutableInteger() {}
+    public MutableInteger(int val) {
+        this.value = val;
+    }
+
+    public int get() { return value;}
+    public void set(int val) {this.value = val;}
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/BCELComparator.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/BCELComparator.java
new file mode 100644
index 0000000..be5f3a6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/BCELComparator.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.util;
+
+/**
+ * Used for BCEL comparison strategy
+ * 
+ * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @version $Id: BCELComparator.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ * @since 5.2
+ */
+public interface BCELComparator {
+
+    /**
+     * Compare two objects and return what THIS.equals(THAT) should return
+     * 
+     * @param THIS
+     * @param THAT
+     * @return true if and only if THIS equals THAT
+     */
+    public boolean equals( Object THIS, Object THAT );
+
+
+    /**
+     * Return hashcode for THIS.hashCode()
+     * 
+     * @param THIS
+     * @return hashcode for THIS.hashCode()
+     */
+    public int hashCode( Object THIS );
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/ByteSequence.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/ByteSequence.java
new file mode 100644
index 0000000..d659387
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/ByteSequence.java
@@ -0,0 +1,58 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+
+/**
+ * Utility class that implements a sequence of bytes which can be read
+ * via the `readByte()' method. This is used to implement a wrapper for the 
+ * Java byte code stream to gain some more readability.
+ *
+ * @version $Id: ByteSequence.java,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public final class ByteSequence extends DataInputStream {
+
+    private ByteArrayStream byte_stream;
+
+
+    public ByteSequence(byte[] bytes) {
+        super(new ByteArrayStream(bytes));
+        byte_stream = (ByteArrayStream) in;
+    }
+
+
+    public final int getIndex() {
+        return byte_stream.getPosition();
+    }
+
+
+    private static final class ByteArrayStream extends ByteArrayInputStream {
+
+        ByteArrayStream(byte[] bytes) {
+            super(bytes);
+        }
+
+
+        final int getPosition() {
+            return pos;
+        } // is protected in ByteArrayInputStream
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/package.html
new file mode 100644
index 0000000..4bbbc02
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/util/package.html
@@ -0,0 +1,41 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+$Id: package.html,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+-->
+</head>
+<body bgcolor="white">
+<p>
+This package contains utility classes for the
+<a href="http://jakarta.apache.org/bcel/">Byte Code Engineering
+Library</a>, namely:
+</p>
+<p>
+<ul>
+<li>Collection classes for JavaClass objects</li>
+<li>A converter for class files to HTML</li>
+<li>A tool to find instructions patterns via regular expressions</li>
+<li>A class to find classes as defined in the CLASSPATH</li>
+<li>A class loader that allows to create classes at run time</li>
+</ul>
+
+</p>
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/Ascii.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/Ascii.java
new file mode 100644
index 0000000..25bb515
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/Ascii.java
@@ -0,0 +1,247 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+/**
+ * This class implements some basic ASCII character handling functions.
+ *
+ * @author dac@eng.sun.com
+ * @author James Todd [gonzo@eng.sun.com]
+ */
+public final class Ascii {
+    /*
+     * Character translation tables.
+     */
+
+    private static final byte[] toUpper = new byte[256];
+    private static final byte[] toLower = new byte[256];
+
+    /*
+     * Character type tables.
+     */
+
+    private static final boolean[] isAlpha = new boolean[256];
+    private static final boolean[] isUpper = new boolean[256];
+    private static final boolean[] isLower = new boolean[256];
+    private static final boolean[] isWhite = new boolean[256];
+    private static final boolean[] isDigit = new boolean[256];
+
+    /*
+     * Initialize character translation and type tables.
+     */
+
+    static {
+        for (int i = 0; i < 256; i++) {
+            toUpper[i] = (byte)i;
+            toLower[i] = (byte)i;
+        }
+
+        for (int lc = 'a'; lc <= 'z'; lc++) {
+            int uc = lc + 'A' - 'a';
+
+            toUpper[lc] = (byte)uc;
+            toLower[uc] = (byte)lc;
+            isAlpha[lc] = true;
+            isAlpha[uc] = true;
+            isLower[lc] = true;
+            isUpper[uc] = true;
+        }
+
+        isWhite[ ' '] = true;
+        isWhite['\t'] = true;
+        isWhite['\r'] = true;
+        isWhite['\n'] = true;
+        isWhite['\f'] = true;
+        isWhite['\b'] = true;
+
+        for (int d = '0'; d <= '9'; d++) {
+            isDigit[d] = true;
+        }
+    }
+
+    /**
+     * Returns the upper case equivalent of the specified ASCII character.
+     */
+
+    public static int toUpper(int c) {
+        return toUpper[c & 0xff] & 0xff;
+    }
+
+    /**
+     * Returns the lower case equivalent of the specified ASCII character.
+     */
+
+    public static int toLower(int c) {
+        return toLower[c & 0xff] & 0xff;
+    }
+
+    /**
+     * Returns true if the specified ASCII character is upper or lower case.
+     */
+
+    public static boolean isAlpha(int c) {
+        return isAlpha[c & 0xff];
+    }
+
+    /**
+     * Returns true if the specified ASCII character is upper case.
+     */
+
+    public static boolean isUpper(int c) {
+        return isUpper[c & 0xff];
+    }
+
+    /**
+     * Returns true if the specified ASCII character is lower case.
+     */
+
+    public static boolean isLower(int c) {
+        return isLower[c & 0xff];
+    }
+
+    /**
+     * Returns true if the specified ASCII character is white space.
+     */
+
+    public static boolean isWhite(int c) {
+        return isWhite[c & 0xff];
+    }
+
+    /**
+     * Returns true if the specified ASCII character is a digit.
+     */
+
+    public static boolean isDigit(int c) {
+        return isDigit[c & 0xff];
+    }
+
+    /**
+     * Parses an unsigned integer from the specified subarray of bytes.
+     * @param b the bytes to parse
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     * @exception NumberFormatException if the integer format was invalid
+     */
+    public static int parseInt(byte[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        int n = c - '0';
+
+        while (--len > 0) {
+            if (!isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            n = n * 10 + c - '0';
+        }
+
+        return n;
+    }
+
+    public static int parseInt(char[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        int n = c - '0';
+
+        while (--len > 0) {
+            if (!isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            n = n * 10 + c - '0';
+        }
+
+        return n;
+    }
+
+    /**
+     * Parses an unsigned long from the specified subarray of bytes.
+     * @param b the bytes to parse
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     * @exception NumberFormatException if the long format was invalid
+     */
+    public static long parseLong(byte[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        long n = c - '0';
+        long m;
+        
+        while (--len > 0) {
+            if (!isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            m = n * 10 + c - '0';
+
+            if (m < n) {
+                // Overflow
+                throw new NumberFormatException();
+            } else {
+                n = m;
+            }
+        }
+
+        return n;
+    }
+
+    public static long parseLong(char[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        long n = c - '0';
+        long m;
+
+        while (--len > 0) {
+            if (!isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            m = n * 10 + c - '0';
+
+            if (m < n) {
+                // Overflow
+                throw new NumberFormatException();
+            } else {
+                n = m;
+            }
+        }
+
+        return n;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/B2CConverter.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/B2CConverter.java
new file mode 100644
index 0000000..1b40f4a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/B2CConverter.java
@@ -0,0 +1,195 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+
+package org.apache.tomcat.util.buf;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+/** Efficient conversion of bytes  to character .
+ *  
+ *  This uses the standard JDK mechanism - a reader - but provides mechanisms
+ *  to recycle all the objects that are used. It is compatible with JDK1.1
+ *  and up,
+ *  ( nio is better, but it's not available even in 1.2 or 1.3 )
+ *
+ *  Not used in the current code, the performance gain is not very big
+ *  in the current case ( since String is created anyway ), but it will
+ *  be used in a later version or after the remaining optimizations.
+ */
+public class B2CConverter {
+    
+    
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( B2CConverter.class );
+    
+    private IntermediateInputStream iis;
+    private ReadConvertor conv;
+    private String encoding;
+
+    protected B2CConverter() {
+    }
+    
+    /** Create a converter, with bytes going to a byte buffer
+     */
+    public B2CConverter(String encoding)
+        throws IOException
+    {
+        this.encoding=encoding;
+        reset();
+    }
+
+    
+    /** Reset the internal state, empty the buffers.
+     *  The encoding remain in effect, the internal buffers remain allocated.
+     */
+    public  void recycle() {
+        conv.recycle();
+    }
+
+    static final int BUFFER_SIZE=8192;
+    char result[]=new char[BUFFER_SIZE];
+
+    public void convert( ByteChunk bb, CharChunk cb, int limit) 
+        throws IOException
+    {
+        iis.setByteChunk( bb );
+        try {
+            // read from the reader
+            int bbLengthBeforeRead = 0;
+            while( limit > 0 ) { // conv.ready() ) {
+                int size = limit < BUFFER_SIZE ? limit : BUFFER_SIZE;
+                bbLengthBeforeRead = bb.getLength();
+                int cnt=conv.read( result, 0, size );
+                if( cnt <= 0 ) {
+                    // End of stream ! - we may be in a bad state
+                    if(log.isDebugEnabled())
+                        log.debug("B2CConverter: EOF");
+                    return;
+                }
+                if(log.isDebugEnabled())
+                    log.debug("B2CConverter: Converted: " +
+                            new String(result, 0, cnt));
+                cb.append( result, 0, cnt );
+                limit = limit - (bbLengthBeforeRead - bb.getLength());
+            }
+        } catch( IOException ex) {
+            if(log.isDebugEnabled())
+                log.debug("B2CConverter: Reseting the converter " + ex.toString());
+            reset();
+            throw ex;
+        }
+    }
+
+
+    public void reset()
+        throws IOException
+    {
+        // destroy the reader/iis
+        iis=new IntermediateInputStream();
+        conv=new ReadConvertor( iis, encoding );
+    }
+
+}
+
+// -------------------- Private implementation --------------------
+
+
+
+/**
+ * 
+ */
+final class  ReadConvertor extends InputStreamReader {
+    
+    /** Create a converter.
+     */
+    public ReadConvertor( IntermediateInputStream in, String enc )
+        throws UnsupportedEncodingException
+    {
+        super( in, enc );
+    }
+    
+    /** Overridden - will do nothing but reset internal state.
+     */
+    @Override
+    public  final void close() throws IOException {
+        // NOTHING
+        // Calling super.close() would reset out and cb.
+    }
+    
+    @Override
+    public  final int read(char cbuf[], int off, int len)
+        throws IOException
+    {
+        // will do the conversion and call write on the output stream
+        return super.read( cbuf, off, len );
+    }
+    
+    /** Reset the buffer
+     */
+    public  final void recycle() {
+        try {
+            // Must clear super's buffer.
+            while (ready()) {
+                // InputStreamReader#skip(long) will allocate buffer to skip.
+                read();
+            }
+        } catch(IOException ioe){
+        }
+    }
+}
+
+
+/** Special output stream where close() is overridden, so super.close()
+    is never called.
+    
+    This allows recycling. It can also be disabled, so callbacks will
+    not be called if recycling the converter and if data was not flushed.
+*/
+final class IntermediateInputStream extends InputStream {
+    ByteChunk bc = null;
+    
+    public IntermediateInputStream() {
+    }
+    
+    @Override
+    public  final void close() throws IOException {
+        // shouldn't be called - we filter it out in writer
+        throw new IOException("close() called - shouldn't happen ");
+    }
+    
+    @Override
+    public  final  int read(byte cbuf[], int off, int len) throws IOException {
+        return bc.substract(cbuf, off, len);
+    }
+    
+    @Override
+    public  final int read() throws IOException {
+        return bc.substract();
+    }
+
+    // -------------------- Internal methods --------------------
+
+
+    void setByteChunk( ByteChunk mb ) {
+        bc = mb;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/ByteChunk.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/ByteChunk.java
new file mode 100644
index 0000000..960d1b1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/ByteChunk.java
@@ -0,0 +1,872 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/*
+ * In a server it is very important to be able to operate on
+ * the original byte[] without converting everything to chars.
+ * Some protocols are ASCII only, and some allow different
+ * non-UNICODE encodings. The encoding is not known beforehand,
+ * and can even change during the execution of the protocol.
+ * ( for example a multipart message may have parts with different
+ *  encoding )
+ *
+ * For HTTP it is not very clear how the encoding of RequestURI
+ * and mime values can be determined, but it is a great advantage
+ * to be able to parse the request without converting to string.
+ */
+
+// TODO: This class could either extend ByteBuffer, or better a ByteBuffer
+// inside this way it could provide the search/etc on ByteBuffer, as a helper.
+
+/**
+ * This class is used to represent a chunk of bytes, and
+ * utilities to manipulate byte[].
+ *
+ * The buffer can be modified and used for both input and output.
+ *
+ * There are 2 modes: The chunk can be associated with a sink - ByteInputChannel
+ * or ByteOutputChannel, which will be used when the buffer is empty (on input)
+ * or filled (on output).
+ * For output, it can also grow. This operating mode is selected by calling
+ * setLimit() or allocate(initial, limit) with limit != -1.
+ *
+ * Various search and append method are defined - similar with String and
+ * StringBuffer, but operating on bytes.
+ *
+ * This is important because it allows processing the http headers directly on
+ * the received bytes, without converting to chars and Strings until the strings
+ * are needed. In addition, the charset is determined later, from headers or
+ * user code.
+ *
+ * @author dac@sun.com
+ * @author James Todd [gonzo@sun.com]
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public final class ByteChunk implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** Input interface, used when the buffer is empty
+     *
+     * Same as java.nio.channel.ReadableByteChannel
+     */
+    public static interface ByteInputChannel {
+        /** 
+         * Read new bytes ( usually the internal conversion buffer ).
+         * The implementation is allowed to ignore the parameters, 
+         * and mutate the chunk if it wishes to implement its own buffering.
+         */
+        public int realReadBytes(byte cbuf[], int off, int len)
+            throws IOException;
+    }
+
+    /** Same as java.nio.channel.WrittableByteChannel.
+     */
+    public static interface ByteOutputChannel {
+        /** 
+         * Send the bytes ( usually the internal conversion buffer ).
+         * Expect 8k output if the buffer is full.
+         */
+        public void realWriteBytes(byte cbuf[], int off, int len)
+            throws IOException;
+    }
+
+    // --------------------
+
+    /** Default encoding used to convert to strings. It should be UTF8,
+        as most standards seem to converge, but the servlet API requires
+        8859_1, and this object is used mostly for servlets. 
+    */
+    public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1";
+        
+    // byte[]
+    private byte[] buff;
+
+    private int start=0;
+    private int end;
+
+    private String enc;
+
+    private boolean isSet=false; // XXX
+
+    // How much can it grow, when data is added
+    private int limit=-1;
+
+    private ByteInputChannel in = null;
+    private ByteOutputChannel out = null;
+
+    private boolean optimizedWrite=true;
+    
+    /**
+     * Creates a new, uninitialized ByteChunk object.
+     */
+    public ByteChunk() {
+        // NO-OP
+    }
+
+    public ByteChunk( int initial ) {
+        allocate( initial, -1 );
+    }
+
+    //--------------------
+    public ByteChunk getClone() {
+        try {
+            return (ByteChunk)this.clone();
+        } catch( Exception ex) {
+            return null;
+        }
+    }
+
+    public boolean isNull() {
+        return ! isSet; // buff==null;
+    }
+    
+    /**
+     * Resets the message buff to an uninitialized state.
+     */
+    public void recycle() {
+        //        buff = null;
+        enc=null;
+        start=0;
+        end=0;
+        isSet=false;
+    }
+
+    public void reset() {
+        buff=null;
+    }
+
+    // -------------------- Setup --------------------
+
+    public void allocate( int initial, int limit  ) {
+        if( buff==null || buff.length < initial ) {
+            buff=new byte[initial];
+        }    
+        this.limit=limit;
+        start=0;
+        end=0;
+        isSet=true;
+    }
+
+    /**
+     * Sets the message bytes to the specified subarray of bytes.
+     * 
+     * @param b the ascii bytes
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     */
+    public void setBytes(byte[] b, int off, int len) {
+        buff = b;
+        start = off;
+        end = start+ len;
+        isSet=true;
+    }
+
+    public void setOptimizedWrite(boolean optimizedWrite) {
+        this.optimizedWrite = optimizedWrite;
+    }
+
+    public void setEncoding( String enc ) {
+        this.enc=enc;
+    }
+    public String getEncoding() {
+        if (enc == null)
+            enc=DEFAULT_CHARACTER_ENCODING;
+        return enc;
+    }
+
+    /**
+     * Returns the message bytes.
+     */
+    public byte[] getBytes() {
+        return getBuffer();
+    }
+
+    /**
+     * Returns the message bytes.
+     */
+    public byte[] getBuffer() {
+        return buff;
+    }
+
+    /**
+     * Returns the start offset of the bytes.
+     * For output this is the end of the buffer.
+     */
+    public int getStart() {
+        return start;
+    }
+
+    public int getOffset() {
+        return start;
+    }
+
+    public void setOffset(int off) {
+        if (end < off ) end=off;
+        start=off;
+    }
+
+    /**
+     * Returns the length of the bytes.
+     * XXX need to clean this up
+     */
+    public int getLength() {
+        return end-start;
+    }
+
+    /** Maximum amount of data in this buffer.
+     *
+     *  If -1 or not set, the buffer will grow indefinitely.
+     *  Can be smaller than the current buffer size ( which will not shrink ).
+     *  When the limit is reached, the buffer will be flushed ( if out is set )
+     *  or throw exception.
+     */
+    public void setLimit(int limit) {
+        this.limit=limit;
+    }
+    
+    public int getLimit() {
+        return limit;
+    }
+
+    /**
+     * When the buffer is empty, read the data from the input channel.
+     */
+    public void setByteInputChannel(ByteInputChannel in) {
+        this.in = in;
+    }
+
+    /** When the buffer is full, write the data to the output channel.
+     *         Also used when large amount of data is appended.
+     *
+     *  If not set, the buffer will grow to the limit.
+     */
+    public void setByteOutputChannel(ByteOutputChannel out) {
+        this.out=out;
+    }
+
+    public int getEnd() {
+        return end;
+    }
+
+    public void setEnd( int i ) {
+        end=i;
+    }
+
+    // -------------------- Adding data to the buffer --------------------
+    /** Append a char, by casting it to byte. This IS NOT intended for unicode.
+     *
+     * @param c
+     * @throws IOException
+     */
+    public void append( char c )
+        throws IOException
+    {
+        append( (byte)c);
+    }
+
+    public void append( byte b )
+        throws IOException
+    {
+        makeSpace( 1 );
+
+        // couldn't make space
+        if( limit >0 && end >= limit ) {
+            flushBuffer();
+        }
+        buff[end++]=b;
+    }
+
+    public void append( ByteChunk src )
+        throws IOException
+    {
+        append( src.getBytes(), src.getStart(), src.getLength());
+    }
+
+    /** Add data to the buffer
+     */
+    public void append( byte src[], int off, int len )
+        throws IOException
+    {
+        // will grow, up to limit
+        makeSpace( len );
+
+        // if we don't have limit: makeSpace can grow as it wants
+        if( limit < 0 ) {
+            // assert: makeSpace made enough space
+            System.arraycopy( src, off, buff, end, len );
+            end+=len;
+            return;
+        }
+
+        // Optimize on a common case.
+        // If the buffer is empty and the source is going to fill up all the
+        // space in buffer, may as well write it directly to the output,
+        // and avoid an extra copy
+        if ( optimizedWrite && len == limit && end == start && out != null ) {
+            out.realWriteBytes( src, off, len );
+            return;
+        }
+        // if we have limit and we're below
+        if( len <= limit - end ) {
+            // makeSpace will grow the buffer to the limit,
+            // so we have space
+            System.arraycopy( src, off, buff, end, len );
+            end+=len;
+            return;
+        }
+
+        // need more space than we can afford, need to flush
+        // buffer
+
+        // the buffer is already at ( or bigger than ) limit
+
+        // We chunk the data into slices fitting in the buffer limit, although
+        // if the data is written directly if it doesn't fit
+
+        int avail=limit-end;
+        System.arraycopy(src, off, buff, end, avail);
+        end += avail;
+
+        flushBuffer();
+
+        int remain = len - avail;
+
+        while (remain > (limit - end)) {
+            out.realWriteBytes( src, (off + len) - remain, limit - end );
+            remain = remain - (limit - end);
+        }
+
+        System.arraycopy(src, (off + len) - remain, buff, end, remain);
+        end += remain;
+
+    }
+
+
+    // -------------------- Removing data from the buffer --------------------
+
+    public int substract()
+        throws IOException {
+
+        if ((end - start) == 0) {
+            if (in == null)
+                return -1;
+            int n = in.realReadBytes( buff, 0, buff.length );
+            if (n < 0)
+                return -1;
+        }
+
+        return (buff[start++] & 0xFF);
+
+    }
+
+    public int substract(ByteChunk src)
+        throws IOException {
+
+        if ((end - start) == 0) {
+            if (in == null)
+                return -1;
+            int n = in.realReadBytes( buff, 0, buff.length );
+            if (n < 0)
+                return -1;
+        }
+
+        int len = getLength();
+        src.append(buff, start, len);
+        start = end;
+        return len;
+
+    }
+
+    public int substract( byte src[], int off, int len )
+        throws IOException {
+
+        if ((end - start) == 0) {
+            if (in == null)
+                return -1;
+            int n = in.realReadBytes( buff, 0, buff.length );
+            if (n < 0)
+                return -1;
+        }
+
+        int n = len;
+        if (len > getLength()) {
+            n = getLength();
+        }
+        System.arraycopy(buff, start, src, off, n);
+        start += n;
+        return n;
+
+    }
+
+
+    /**
+     * Send the buffer to the sink. Called by append() when the limit is
+     * reached. You can also call it explicitly to force the data to be written.
+     *
+     * @throws IOException
+     */
+    public void flushBuffer()
+        throws IOException
+    {
+        //assert out!=null
+        if( out==null ) {
+            throw new IOException( "Buffer overflow, no sink " + limit + " " +
+                                   buff.length  );
+        }
+        out.realWriteBytes( buff, start, end-start );
+        end=start;
+    }
+
+    /** Make space for len chars. If len is small, allocate
+     *        a reserve space too. Never grow bigger than limit.
+     */
+    private void makeSpace(int count)
+    {
+        byte[] tmp = null;
+
+        int newSize;
+        int desiredSize=end + count;
+
+        // Can't grow above the limit
+        if( limit > 0 &&
+            desiredSize > limit) {
+            desiredSize=limit;
+        }
+
+        if( buff==null ) {
+            if( desiredSize < 256 ) desiredSize=256; // take a minimum
+            buff=new byte[desiredSize];
+        }
+        
+        // limit < buf.length ( the buffer is already big )
+        // or we already have space XXX
+        if( desiredSize <= buff.length ) {
+            return;
+        }
+        // grow in larger chunks
+        if( desiredSize < 2 * buff.length ) {
+            newSize= buff.length * 2;
+            if( limit >0 &&
+                newSize > limit ) newSize=limit;
+            tmp=new byte[newSize];
+        } else {
+            newSize= buff.length * 2 + count ;
+            if( limit > 0 &&
+                newSize > limit ) newSize=limit;
+            tmp=new byte[newSize];
+        }
+        
+        System.arraycopy(buff, start, tmp, 0, end-start);
+        buff = tmp;
+        tmp = null;
+        end=end-start;
+        start=0;
+    }
+    
+    // -------------------- Conversion and getters --------------------
+
+    @Override
+    public String toString() {
+        if (null == buff) {
+            return null;
+        } else if (end-start == 0) {
+            return "";
+        }
+        return StringCache.toString(this);
+    }
+    
+    public String toStringInternal() {
+        String strValue=null;
+        try {
+            if( enc==null ) enc=DEFAULT_CHARACTER_ENCODING;
+            strValue = new String( buff, start, end-start, enc );
+            /*
+             Does not improve the speed too much on most systems,
+             it's safer to use the "classical" new String().
+             
+             Most overhead is in creating char[] and copying,
+             the internal implementation of new String() is very close to
+             what we do. The decoder is nice for large buffers and if
+             we don't go to String ( so we can take advantage of reduced GC)
+             
+             // Method is commented out, in:
+              return B2CConverter.decodeString( enc );
+              */
+        } catch (java.io.UnsupportedEncodingException e) {
+            // Use the platform encoding in that case; the usage of a bad
+            // encoding will have been logged elsewhere already
+            strValue = new String(buff, start, end-start);
+        }
+        return strValue;
+    }
+
+    public int getInt()
+    {
+        return Ascii.parseInt(buff, start,end-start);
+    }
+
+    public long getLong() {
+        return Ascii.parseLong(buff, start,end-start);
+    }
+
+
+    // -------------------- equals --------------------
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equals(String s) {
+        // XXX ENCODING - this only works if encoding is UTF8-compat
+        // ( ok for tomcat, where we compare ascii - header names, etc )!!!
+        
+        byte[] b = buff;
+        int blen = end-start;
+        if (b == null || blen != s.length()) {
+            return false;
+        }
+        int boff = start;
+        for (int i = 0; i < blen; i++) {
+            if (b[boff++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equalsIgnoreCase(String s) {
+        byte[] b = buff;
+        int blen = end-start;
+        if (b == null || blen != s.length()) {
+            return false;
+        }
+        int boff = start;
+        for (int i = 0; i < blen; i++) {
+            if (Ascii.toLower(b[boff++]) != Ascii.toLower(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean equals( ByteChunk bb ) {
+        return equals( bb.getBytes(), bb.getStart(), bb.getLength());
+    }
+    
+    public boolean equals( byte b2[], int off2, int len2) {
+        byte b1[]=buff;
+        if( b1==null && b2==null ) return true;
+
+        int len=end-start;
+        if ( len2 != len || b1==null || b2==null ) 
+            return false;
+                
+        int off1 = start;
+
+        while ( len-- > 0) {
+            if (b1[off1++] != b2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean equals( CharChunk cc ) {
+        return equals( cc.getChars(), cc.getStart(), cc.getLength());
+    }
+    
+    public boolean equals( char c2[], int off2, int len2) {
+        // XXX works only for enc compatible with ASCII/UTF !!!
+        byte b1[]=buff;
+        if( c2==null && b1==null ) return true;
+        
+        if (b1== null || c2==null || end-start != len2 ) {
+            return false;
+        }
+        int off1 = start;
+        int len=end-start;
+        
+        while ( len-- > 0) {
+            if ( (char)b1[off1++] != c2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     */
+    public boolean startsWith(String s) {
+        // Works only if enc==UTF
+        byte[] b = buff;
+        int blen = s.length();
+        if (b == null || blen > end-start) {
+            return false;
+        }
+        int boff = start;
+        for (int i = 0; i < blen; i++) {
+            if (b[boff++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /* Returns true if the message bytes start with the specified byte array */
+    public boolean startsWith(byte[] b2) {
+        byte[] b1 = buff;
+        if (b1 == null && b2 == null) {
+            return true;
+        }
+
+        int len = end - start;
+        if (b1 == null || b2 == null || b2.length > len) {
+            return false;
+        }
+        for (int i = start, j = 0; i < end && j < b2.length;) {
+            if (b1[i++] != b2[j++]) 
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     * @param pos The position
+     */
+    public boolean startsWithIgnoreCase(String s, int pos) {
+        byte[] b = buff;
+        int len = s.length();
+        if (b == null || len+pos > end-start) {
+            return false;
+        }
+        int off = start+pos;
+        for (int i = 0; i < len; i++) {
+            if (Ascii.toLower( b[off++] ) != Ascii.toLower( s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public int indexOf( String src, int srcOff, int srcLen, int myOff ) {
+        char first=src.charAt( srcOff );
+
+        // Look for first char 
+        int srcEnd = srcOff + srcLen;
+        
+        mainLoop:
+        for( int i=myOff+start; i <= (end - srcLen); i++ ) {
+            if( buff[i] != first ) continue;
+            // found first char, now look for a match
+            int myPos=i+1;
+            for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
+                if( buff[myPos++] != src.charAt( srcPos++ ))
+                    continue mainLoop;
+            }
+            return i-start; // found it
+        }
+        return -1;
+    }
+
+    // -------------------- Hash code  --------------------
+
+    // normal hash. 
+    public int hash() {
+        return hashBytes( buff, start, end-start);
+    }
+
+    // hash ignoring case
+    public int hashIgnoreCase() {
+        return hashBytesIC( buff, start, end-start );
+    }
+
+    private static int hashBytes( byte buff[], int start, int bytesLen ) {
+        int max=start+bytesLen;
+        byte bb[]=buff;
+        int code=0;
+        for (int i = start; i < max ; i++) {
+            code = code * 37 + bb[i];
+        }
+        return code;
+    }
+
+    private static int hashBytesIC( byte bytes[], int start,
+                                    int bytesLen )
+    {
+        int max=start+bytesLen;
+        byte bb[]=bytes;
+        int code=0;
+        for (int i = start; i < max ; i++) {
+            code = code * 37 + Ascii.toLower(bb[i]);
+        }
+        return code;
+    }
+
+    /**
+     * Returns the first instance of the given character in this ByteChunk
+     * starting at the specified byte. If the character is not found, -1 is
+     * returned.
+     * <br/>
+     * NOTE: This only works for characters in the range 0-127.
+     * 
+     * @param c         The character
+     * @param starting  The start position
+     * @return          The position of the first instance of the character or
+     *                      -1 if the character is not found.
+     */
+    public int indexOf(char c, int starting) {
+        int ret = indexOf(buff, start + starting, end, c);
+        return (ret >= start) ? ret - start : -1;
+    }
+
+    /**
+     * Returns the first instance of the given character in the given byte array
+     * between the specified start and end.
+     * <br/>
+     * NOTE: This only works for characters in the range 0-127.
+     * 
+     * @param bytes The byte array to search
+     * @param start The point to start searching from in the byte array
+     * @param end   The point to stop searching in the byte array
+     * @param c     The character to search for 
+     * @return      The position of the first instance of the character or -1
+     *                  if the character is not found.
+     */
+    public static int indexOf(byte bytes[], int start, int end, char c) {
+        int offset = start;
+        
+        while (offset < end) {
+            byte b=bytes[offset];
+            if (b == c)
+                return offset;
+            offset++;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the first instance of the given byte in the byte array between
+     * the specified start and end.
+     * 
+     * @param bytes The byte array to search
+     * @param start The point to start searching from in the byte array
+     * @param end   The point to stop searching in the byte array
+     * @param b     The byte to search for 
+     * @return      The position of the first instance of the byte or -1 if the
+     *                  byte is not found.
+     */
+    public static int findByte(byte bytes[], int start, int end, byte b) {
+        int offset = start;
+        while (offset < end) {
+            if (bytes[offset] == b) {
+                return offset;
+            }
+            offset++;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the first instance of any of the given bytes in the byte array
+     * between the specified start and end.
+     * 
+     * @param bytes The byte array to search
+     * @param start The point to start searching from in the byte array
+     * @param end   The point to stop searching in the byte array
+     * @param b     The array of bytes to search for 
+     * @return      The position of the first instance of the byte or -1 if the
+     *                  byte is not found.
+     */
+    public static int findBytes(byte bytes[], int start, int end, byte b[]) {
+        int blen = b.length;
+        int offset = start;
+        while (offset < end) {
+            for (int i = 0;  i < blen; i++) 
+                if (bytes[offset] == b[i]) {
+                    return offset;
+                }
+            offset++;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the first instance of any byte that is not one of the given bytes
+     * in the byte array between the specified start and end.
+     * 
+     * @param bytes The byte array to search
+     * @param start The point to start searching from in the byte array
+     * @param end   The point to stop searching in the byte array
+     * @param b     The list of bytes to search for 
+     * @return      The position of the first instance a byte that is not
+     *                  in the list of bytes to search for or -1 if no such byte
+     *                  is found.
+     */
+    public static int findNotBytes(byte bytes[], int start, int end, byte b[]) {
+        int blen = b.length;
+        int offset = start;
+        boolean found;
+                
+        while (offset < end) {
+            found = true;
+            for (int i = 0; i < blen; i++) {
+                if (bytes[offset] == b[i]) {
+                    found=false;
+                    break;
+                }
+            }
+            if (found) {
+                return offset;
+            }
+            offset++;
+        }
+        return -1;
+    }
+
+
+    /**
+     * Convert specified String to a byte array. This ONLY WORKS for ascii, UTF
+     * chars will be truncated.
+     * 
+     * @param value to convert to byte array
+     * @return the byte array value
+     */
+    public static final byte[] convertToBytes(String value) {
+        byte[] result = new byte[value.length()];
+        for (int i = 0; i < value.length(); i++) {
+            result[i] = (byte) value.charAt(i);
+        }
+        return result;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/C2BConverter.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/C2BConverter.java
new file mode 100644
index 0000000..d75c6b1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/C2BConverter.java
@@ -0,0 +1,275 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+/** Efficient conversion of character to bytes.
+ *  
+ *  This uses the standard JDK mechanism - a writer - but provides mechanisms
+ *  to recycle all the objects that are used. It is compatible with JDK1.1 and up,
+ *  ( nio is better, but it's not available even in 1.2 or 1.3 )
+ * 
+ */
+public final class C2BConverter {
+    
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(C2BConverter.class );
+    
+    private IntermediateOutputStream ios;
+    private WriteConvertor conv;
+    private ByteChunk bb;
+    private String enc;
+    
+    /** Create a converter, with bytes going to a byte buffer
+     */
+    public C2BConverter(ByteChunk output, String encoding) throws IOException {
+        this.bb=output;
+        ios=new IntermediateOutputStream( output );
+        conv=new WriteConvertor( ios, encoding );
+        this.enc=encoding;
+    }
+
+    /** Create a converter
+     */
+    public C2BConverter(String encoding) throws IOException {
+        this( new ByteChunk(1024), encoding );
+    }
+
+    public ByteChunk getByteChunk() {
+        return bb;
+    }
+
+    public String getEncoding() {
+        return enc;
+    }
+
+    public void setByteChunk(ByteChunk bb) {
+        this.bb=bb;
+        ios.setByteChunk( bb );
+    }
+
+    /** Reset the internal state, empty the buffers.
+     *  The encoding remain in effect, the internal buffers remain allocated.
+     */
+    public  final void recycle() {
+        conv.recycle();
+        bb.recycle();
+    }
+
+    /** Generate the bytes using the specified encoding
+     */
+    public  final void convert(char c[], int off, int len ) throws IOException {
+        conv.write( c, off, len );
+    }
+
+    /** Generate the bytes using the specified encoding
+     */
+    public  final void convert(String s, int off, int len ) throws IOException {
+        conv.write( s, off, len );
+    }
+
+    /** Generate the bytes using the specified encoding
+     */
+    public  final void convert(String s ) throws IOException {
+        conv.write( s );
+    }
+
+    /** Generate the bytes using the specified encoding
+     */
+    public  final void convert(char c ) throws IOException {
+        conv.write( c );
+    }
+
+    /** Convert a message bytes chars to bytes
+     */
+    public final void convert(MessageBytes mb ) throws IOException {
+        int type=mb.getType();
+        if( type==MessageBytes.T_BYTES )
+            return;
+        ByteChunk orig=bb;
+        setByteChunk( mb.getByteChunk());
+        bb.recycle();
+        bb.allocate( 32, -1 );
+        
+        if( type==MessageBytes.T_STR ) {
+            convert( mb.getString() );
+            // System.out.println("XXX Converting " + mb.getString() );
+        } else if( type==MessageBytes.T_CHARS ) {
+            CharChunk charC=mb.getCharChunk();
+            convert( charC.getBuffer(),
+                                charC.getOffset(), charC.getLength());
+            //System.out.println("XXX Converting " + mb.getCharChunk() );
+        } else {
+            if (log.isDebugEnabled())
+                log.debug("XXX unknowon type " + type );
+        }
+        flushBuffer();
+        //System.out.println("C2B: XXX " + bb.getBuffer() + bb.getLength()); 
+        setByteChunk(orig);
+    }
+
+    /** Flush any internal buffers into the ByteOutput or the internal
+     *  byte[]
+     */
+    public  final void flushBuffer() throws IOException {
+        conv.flush();
+    }
+
+}
+
+// -------------------- Private implementation --------------------
+
+
+
+/**
+ *  Special writer class, where close() is overridden. The default implementation
+ *  would set byteOutputter to null, and the writer can't be recycled. 
+ *
+ *  Note that the flush method will empty the internal buffers _and_ call
+ *  flush on the output stream - that's why we use an intermediary output stream
+ *  that overrides flush(). The idea is to  have full control: flushing the
+ *  char->byte converter should be independent of flushing the OutputStream.
+ * 
+ *  When a WriteConverter is created, it'll allocate one or 2 byte buffers,
+ *  with a 8k size that can't be changed ( at least in JDK1.1 -> 1.4 ). It would
+ *  also allocate a ByteOutputter or equivalent - again some internal buffers.
+ *
+ *  It is essential to keep  this object around and reuse it. You can use either
+ *  pools or per thread data - but given that in most cases a converter will be
+ *  needed for every thread and most of the time only 1 ( or 2 ) encodings will
+ *  be used, it is far better to keep it per thread and eliminate the pool 
+ *  overhead too.
+ * 
+ */
+ final class WriteConvertor extends OutputStreamWriter {
+    // stream with flush() and close(). overridden.
+    private IntermediateOutputStream ios;
+    
+    // Has a private, internal byte[8192]
+    
+    /** Create a converter.
+     */
+    public WriteConvertor( IntermediateOutputStream out, String enc )
+        throws UnsupportedEncodingException
+    {
+        super( out, enc );
+        ios=out;
+    }
+    
+    /** Overridden - will do nothing but reset internal state.
+     */
+    @Override
+    public  final void close() throws IOException {
+        // NOTHING
+        // Calling super.close() would reset out and cb.
+    }
+    
+    /**
+     *  Flush the characters only
+     */ 
+    @Override
+    public  final void flush() throws IOException {
+        // Will flushBuffer and out()
+        // flushBuffer put any remaining chars in the byte[] 
+        super.flush();
+    }
+    
+    @Override
+    public  final void write(char cbuf[], int off, int len) throws IOException {
+        // will do the conversion and call write on the output stream
+        super.write( cbuf, off, len );
+    }
+    
+    /** Reset the buffer
+     */
+    public  final void recycle() {
+        ios.disable();
+        try {
+            // System.out.println("Reseting writer");
+            flush();
+        } catch( Exception ex ) {
+            ex.printStackTrace();
+        }
+        ios.enable();
+    }
+    
+}
+
+
+/** Special output stream where close() is overridden, so super.close()
+    is never called.
+    
+    This allows recycling. It can also be disabled, so callbacks will
+    not be called if recycling the converter and if data was not flushed.
+*/
+final class IntermediateOutputStream extends OutputStream {
+    private ByteChunk tbuff;
+    private boolean enabled=true;
+    
+    public IntermediateOutputStream(ByteChunk tbuff) {
+        this.tbuff=tbuff;
+    }
+    
+    @Override
+    public  final void close() throws IOException {
+        // shouldn't be called - we filter it out in writer
+        throw new IOException("close() called - shouldn't happen ");
+    }
+    
+    @Override
+    public  final void flush() throws IOException {
+        // nothing - write will go directly to the buffer,
+        // we don't keep any state
+    }
+    
+    @Override
+    public  final  void write(byte cbuf[], int off, int len) throws IOException {
+        // will do the conversion and call write on the output stream
+        if( enabled ) {
+            tbuff.append( cbuf, off, len );
+        }
+    }
+    
+    @Override
+    public  final void write( int i ) throws IOException {
+        throw new IOException("write( int ) called - shouldn't happen ");
+    }
+
+    // -------------------- Internal methods --------------------
+
+    void setByteChunk( ByteChunk bb ) {
+        tbuff=bb;
+    }
+    
+    /** Temporary disable - this is used to recycle the converter without
+     *  generating an output if the buffers were not flushed
+     */
+    final void disable() {
+        enabled=false;
+    }
+
+    /** Reenable - used to recycle the converter
+     */
+    final void enable() {
+        enabled=true;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/CharChunk.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/CharChunk.java
new file mode 100644
index 0000000..5b5f635
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/CharChunk.java
@@ -0,0 +1,720 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Utilities to manipulate char chunks. While String is
+ * the easiest way to manipulate chars ( search, substrings, etc),
+ * it is known to not be the most efficient solution - Strings are
+ * designed as immutable and secure objects.
+ * 
+ * @author dac@sun.com
+ * @author James Todd [gonzo@sun.com]
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public final class CharChunk implements Cloneable, Serializable, CharSequence {
+
+    private static final long serialVersionUID = 1L;
+
+    // Input interface, used when the buffer is emptied.
+    public static interface CharInputChannel {
+        /** 
+         * Read new bytes ( usually the internal conversion buffer ).
+         * The implementation is allowed to ignore the parameters, 
+         * and mutate the chunk if it wishes to implement its own buffering.
+         */
+        public int realReadChars(char cbuf[], int off, int len)
+            throws IOException;
+    }
+    /**
+     *  When we need more space we'll either
+     *  grow the buffer ( up to the limit ) or send it to a channel.
+     */
+    public static interface CharOutputChannel {
+        /** Send the bytes ( usually the internal conversion buffer ).
+         *  Expect 8k output if the buffer is full.
+         */
+        public void realWriteChars(char cbuf[], int off, int len)
+            throws IOException;
+    }
+    
+    // -------------------- 
+    // char[]
+    private char buff[];
+
+    private int start;
+    private int end;
+
+    private boolean isSet=false;  // XXX 
+
+    // -1: grow indefinitely
+    // maximum amount to be cached
+    private int limit=-1;
+
+    private CharInputChannel in = null;
+    private CharOutputChannel out = null;
+    
+    private boolean optimizedWrite=true;
+
+    /**
+     * Creates a new, uninitialized CharChunk object.
+     */
+    public CharChunk() {
+    }
+
+    public CharChunk(int size) {
+        allocate( size, -1 );
+    }
+
+    // --------------------
+    
+    public CharChunk getClone() {
+        try {
+            return (CharChunk)this.clone();
+        } catch( Exception ex) {
+            return null;
+        }
+    }
+
+    public boolean isNull() {
+        if( end > 0 ) return false;
+        return !isSet; //XXX 
+    }
+    
+    /**
+     * Resets the message bytes to an uninitialized state.
+     */
+    public void recycle() {
+        //        buff=null;
+        isSet=false; // XXX
+        start=0;
+        end=0;
+    }
+
+    public void reset() {
+        buff=null;
+    }
+
+    // -------------------- Setup --------------------
+
+    public void allocate( int initial, int limit  ) {
+        if( buff==null || buff.length < initial ) {
+            buff=new char[initial];
+        }
+        this.limit=limit;
+        start=0;
+        end=0;
+        isSet=true;
+    }
+
+
+    public void setOptimizedWrite(boolean optimizedWrite) {
+        this.optimizedWrite = optimizedWrite;
+    }
+
+    public void setChars( char[] c, int off, int len ) {
+        buff=c;
+        start=off;
+        end=start + len;
+        isSet=true;
+    }
+
+    /** Maximum amount of data in this buffer.
+     *
+     *  If -1 or not set, the buffer will grow indefinitely.
+     *  Can be smaller than the current buffer size ( which will not shrink ).
+     *  When the limit is reached, the buffer will be flushed ( if out is set )
+     *  or throw exception.
+     */
+    public void setLimit(int limit) {
+        this.limit=limit;
+    }
+    
+    public int getLimit() {
+        return limit;
+    }
+
+    /**
+     * When the buffer is empty, read the data from the input channel.
+     */
+    public void setCharInputChannel(CharInputChannel in) {
+        this.in = in;
+    }
+
+    /** When the buffer is full, write the data to the output channel.
+     *         Also used when large amount of data is appended.
+     *
+     *  If not set, the buffer will grow to the limit.
+     */
+    public void setCharOutputChannel(CharOutputChannel out) {
+        this.out=out;
+    }
+
+    // compat 
+    public char[] getChars()
+    {
+        return getBuffer();
+    }
+    
+    public char[] getBuffer()
+    {
+        return buff;
+    }
+    
+    /**
+     * Returns the start offset of the bytes.
+     * For output this is the end of the buffer.
+     */
+    public int getStart() {
+        return start;
+    }
+    
+    public int getOffset() {
+        return start;
+    }
+
+    /**
+     * Returns the start offset of the bytes.
+     */
+    public void setOffset(int off) {
+        start=off;
+    }
+
+    /**
+     * Returns the length of the bytes.
+     */
+    public int getLength() {
+        return end-start;
+    }
+
+
+    public int getEnd() {
+        return end;
+    }
+
+    public void setEnd( int i ) {
+        end=i;
+    }
+
+    // -------------------- Adding data --------------------
+    
+    public void append( char b )
+        throws IOException
+    {
+        makeSpace( 1 );
+
+        // couldn't make space
+        if( limit >0 && end >= limit ) {
+            flushBuffer();
+        }
+        buff[end++]=b;
+    }
+    
+    public void append( CharChunk src )
+        throws IOException
+    {
+        append( src.getBuffer(), src.getOffset(), src.getLength());
+    }
+
+    /** Add data to the buffer
+     */
+    public void append( char src[], int off, int len )
+        throws IOException
+    {
+        // will grow, up to limit
+        makeSpace( len );
+
+        // if we don't have limit: makeSpace can grow as it wants
+        if( limit < 0 ) {
+            // assert: makeSpace made enough space
+            System.arraycopy( src, off, buff, end, len );
+            end+=len;
+            return;
+        }
+
+        // Optimize on a common case.
+        // If the source is going to fill up all the space in buffer, may
+        // as well write it directly to the output, and avoid an extra copy
+        if ( optimizedWrite && len == limit && end == start && out != null ) {
+            out.realWriteChars( src, off, len );
+            return;
+        }
+        
+        // if we have limit and we're below
+        if( len <= limit - end ) {
+            // makeSpace will grow the buffer to the limit,
+            // so we have space
+            System.arraycopy( src, off, buff, end, len );
+            
+            end+=len;
+            return;
+        }
+
+        // need more space than we can afford, need to flush
+        // buffer
+
+        // the buffer is already at ( or bigger than ) limit
+        
+        // Optimization:
+        // If len-avail < length ( i.e. after we fill the buffer with
+        // what we can, the remaining will fit in the buffer ) we'll just
+        // copy the first part, flush, then copy the second part - 1 write
+        // and still have some space for more. We'll still have 2 writes, but
+        // we write more on the first.
+
+        if( len + end < 2 * limit ) {
+            /* If the request length exceeds the size of the output buffer,
+               flush the output buffer and then write the data directly.
+               We can't avoid 2 writes, but we can write more on the second
+            */
+            int avail=limit-end;
+            System.arraycopy(src, off, buff, end, avail);
+            end += avail;
+            
+            flushBuffer();
+            
+            System.arraycopy(src, off+avail, buff, end, len - avail);
+            end+= len - avail;
+            
+        } else {        // len > buf.length + avail
+            // long write - flush the buffer and write the rest
+            // directly from source
+            flushBuffer();
+            
+            out.realWriteChars( src, off, len );
+        }
+    }
+
+
+    /** Add data to the buffer
+     */
+    public void append( StringBuilder sb )
+        throws IOException
+    {
+        int len=sb.length();
+
+        // will grow, up to limit
+        makeSpace( len );
+
+        // if we don't have limit: makeSpace can grow as it wants
+        if( limit < 0 ) {
+            // assert: makeSpace made enough space
+            sb.getChars(0, len, buff, end );
+            end+=len;
+            return;
+        }
+
+        int off=0;
+        int sbOff = off;
+        int sbEnd = off + len;
+        while (sbOff < sbEnd) {
+            int d = min(limit - end, sbEnd - sbOff);
+            sb.getChars( sbOff, sbOff+d, buff, end);
+            sbOff += d;
+            end += d;
+            if (end >= limit)
+                flushBuffer();
+        }
+    }
+
+    /** Append a string to the buffer
+     */
+    public void append(String s) throws IOException {
+        append(s, 0, s.length());
+    }
+    
+    /** Append a string to the buffer
+     */
+    public void append(String s, int off, int len) throws IOException {
+        if (s==null) return;
+        
+        // will grow, up to limit
+        makeSpace( len );
+
+        // if we don't have limit: makeSpace can grow as it wants
+        if( limit < 0 ) {
+            // assert: makeSpace made enough space
+            s.getChars(off, off+len, buff, end );
+            end+=len;
+            return;
+        }
+
+        int sOff = off;
+        int sEnd = off + len;
+        while (sOff < sEnd) {
+            int d = min(limit - end, sEnd - sOff);
+            s.getChars( sOff, sOff+d, buff, end);
+            sOff += d;
+            end += d;
+            if (end >= limit)
+                flushBuffer();
+        }
+    }
+    
+    // -------------------- Removing data from the buffer --------------------
+
+    public int substract()
+        throws IOException {
+
+        if ((end - start) == 0) {
+            if (in == null)
+                return -1;
+            int n = in.realReadChars(buff, end, buff.length - end);
+            if (n < 0)
+                return -1;
+        }
+
+        return (buff[start++]);
+
+    }
+
+    public int substract(CharChunk src)
+        throws IOException {
+
+        if ((end - start) == 0) {
+            if (in == null)
+                return -1;
+            int n = in.realReadChars( buff, end, buff.length - end);
+            if (n < 0)
+                return -1;
+        }
+
+        int len = getLength();
+        src.append(buff, start, len);
+        start = end;
+        return len;
+
+    }
+
+    public int substract( char src[], int off, int len )
+        throws IOException {
+
+        if ((end - start) == 0) {
+            if (in == null)
+                return -1;
+            int n = in.realReadChars( buff, end, buff.length - end);
+            if (n < 0)
+                return -1;
+        }
+
+        int n = len;
+        if (len > getLength()) {
+            n = getLength();
+        }
+        System.arraycopy(buff, start, src, off, n);
+        start += n;
+        return n;
+
+    }
+
+
+    public void flushBuffer()
+        throws IOException
+    {
+        //assert out!=null
+        if( out==null ) {
+            throw new IOException( "Buffer overflow, no sink " + limit + " " +
+                                   buff.length  );
+        }
+        out.realWriteChars( buff, start, end - start );
+        end=start;
+    }
+
+    /** Make space for len chars. If len is small, allocate
+     *        a reserve space too. Never grow bigger than limit.
+     */
+    private void makeSpace(int count)
+    {
+        char[] tmp = null;
+
+        int newSize;
+        int desiredSize=end + count;
+
+        // Can't grow above the limit
+        if( limit > 0 &&
+            desiredSize > limit) {
+            desiredSize=limit;
+        }
+
+        if( buff==null ) {
+            if( desiredSize < 256 ) desiredSize=256; // take a minimum
+            buff=new char[desiredSize];
+        }
+
+        // limit < buf.length ( the buffer is already big )
+        // or we already have space XXX
+        if( desiredSize <= buff.length) {
+            return;
+        }
+        // grow in larger chunks
+        if( desiredSize < 2 * buff.length ) {
+            newSize= buff.length * 2;
+            if( limit >0 &&
+                newSize > limit ) newSize=limit;
+            tmp=new char[newSize];
+        } else {
+            newSize= buff.length * 2 + count ;
+            if( limit > 0 &&
+                newSize > limit ) newSize=limit;
+            tmp=new char[newSize];
+        }
+        
+        System.arraycopy(buff, 0, tmp, 0, end);
+        buff = tmp;
+        tmp = null;
+    }
+    
+    // -------------------- Conversion and getters --------------------
+
+    @Override
+    public String toString() {
+        if (null == buff) {
+            return null;
+        } else if (end-start == 0) {
+            return "";
+        }
+        return StringCache.toString(this);
+    }
+    
+    public String toStringInternal() {
+        return new String(buff, start, end-start);
+    }
+
+    public int getInt()
+    {
+        return Ascii.parseInt(buff, start,
+                                end-start);
+    }
+    
+    // -------------------- equals --------------------
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equals(String s) {
+        char[] c = buff;
+        int len = end-start;
+        if (c == null || len != s.length()) {
+            return false;
+        }
+        int off = start;
+        for (int i = 0; i < len; i++) {
+            if (c[off++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equalsIgnoreCase(String s) {
+        char[] c = buff;
+        int len = end-start;
+        if (c == null || len != s.length()) {
+            return false;
+        }
+        int off = start;
+        for (int i = 0; i < len; i++) {
+            if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean equals(CharChunk cc) {
+        return equals( cc.getChars(), cc.getOffset(), cc.getLength());
+    }
+
+    public boolean equals(char b2[], int off2, int len2) {
+        char b1[]=buff;
+        if( b1==null && b2==null ) return true;
+        
+        if (b1== null || b2==null || end-start != len2) {
+            return false;
+        }
+        int off1 = start;
+        int len=end-start;
+        while ( len-- > 0) {
+            if (b1[off1++] != b2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean equals(byte b2[], int off2, int len2) {
+        char b1[]=buff;
+        if( b2==null && b1==null ) return true;
+
+        if (b1== null || b2==null || end-start != len2) {
+            return false;
+        }
+        int off1 = start;
+        int len=end-start;
+        
+        while ( len-- > 0) {
+            if ( b1[off1++] != (char)b2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     */
+    public boolean startsWith(String s) {
+        char[] c = buff;
+        int len = s.length();
+        if (c == null || len > end-start) {
+            return false;
+        }
+        int off = start;
+        for (int i = 0; i < len; i++) {
+            if (c[off++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     */
+    public boolean startsWithIgnoreCase(String s, int pos) {
+        char[] c = buff;
+        int len = s.length();
+        if (c == null || len+pos > end-start) {
+            return false;
+        }
+        int off = start+pos;
+        for (int i = 0; i < len; i++) {
+            if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+
+    // -------------------- Hash code  --------------------
+
+    // normal hash. 
+    public int hash() {
+        int code=0;
+        for (int i = start; i < start + end-start; i++) {
+            code = code * 37 + buff[i];
+        }
+        return code;
+    }
+
+    // hash ignoring case
+    public int hashIgnoreCase() {
+        int code=0;
+        for (int i = start; i < end; i++) {
+            code = code * 37 + Ascii.toLower(buff[i]);
+        }
+        return code;
+    }
+
+    public int indexOf(char c) {
+        return indexOf( c, start);
+    }
+    
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param c the character
+     */
+    public int indexOf(char c, int starting) {
+        int ret = indexOf( buff, start+starting, end, c );
+        return (ret >= start) ? ret - start : -1;
+    }
+
+    public static int indexOf( char chars[], int off, int cend, char qq )
+    {
+        while( off < cend ) {
+            char b=chars[off];
+            if( b==qq )
+                return off;
+            off++;
+        }
+        return -1;
+    }
+    
+
+    public int indexOf( String src, int srcOff, int srcLen, int myOff ) {
+        char first=src.charAt( srcOff );
+
+        // Look for first char 
+        int srcEnd = srcOff + srcLen;
+        
+        for( int i=myOff+start; i <= (end - srcLen); i++ ) {
+            if( buff[i] != first ) continue;
+            // found first char, now look for a match
+            int myPos=i+1;
+            for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
+                if( buff[myPos++] != src.charAt( srcPos++ ))
+                    break;
+                if( srcPos==srcEnd ) return i-start; // found it
+            }
+        }
+        return -1;
+    }
+
+    // -------------------- utils
+    private int min(int a, int b) {
+        if (a < b) return a;
+        return b;
+    }
+
+    // Char sequence impl
+    
+    public char charAt(int index) {
+        return buff[index + start];
+    }
+    
+    public CharSequence subSequence(int start, int end) {
+        try {
+            CharChunk result = (CharChunk) this.clone();
+            result.setOffset(this.start + start);
+            result.setEnd(this.start + end);
+            return result;
+        } catch (CloneNotSupportedException e) {
+            // Cannot happen
+            return null;
+        }
+    }
+    
+    public int length() {
+        return end - start;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/HexUtils.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/HexUtils.java
new file mode 100644
index 0000000..3afe904
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/HexUtils.java
@@ -0,0 +1,102 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+/**
+ * Tables useful when converting byte arrays to and from strings of hexadecimal
+ * digits.
+ * Code from Ajp11, from Apache's JServ.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public final class HexUtils {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     *  Table for HEX to DEC byte translation.
+     */
+    private static final int[] DEC = {
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        00, 01, 02, 03, 04, 05, 06, 07,  8,  9, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    };
+
+
+    /**
+     * Table for DEC to HEX byte translation.
+     */
+    private static final byte[] HEX = 
+    { (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' };
+
+
+    /**
+     * Table for byte to hex string translation.
+     */
+    private static final char[] hex = "0123456789abcdef".toCharArray();
+
+    // --------------------------------------------------------- Static Methods
+
+
+    /**
+     * Provide a mechanism for ensuring this class is loaded. 
+     */
+    public static void load() {
+        // Nothing to do
+    }
+
+    public static int getDec(int index){
+        return DEC[index];
+    }
+
+    public static byte getHex(int index){
+        return HEX[index];
+    }
+
+    public static String toHexString(byte[] bytes)
+    {
+        if(null == bytes) return null;
+
+        StringBuilder sb = new StringBuilder(bytes.length << 1);
+
+        for(int i=0; i<bytes.length; ++i)
+            sb.append(hex[(bytes[i] & 0xf0) >> 4])
+                .append(hex[(bytes[i] & 0x0f)])
+                ;
+
+        return sb.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/MessageBytes.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/MessageBytes.java
new file mode 100644
index 0000000..9d81094
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/MessageBytes.java
@@ -0,0 +1,646 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Locale;
+
+/**
+ * This class is used to represent a subarray of bytes in an HTTP message.
+ * It represents all request/response elements. The byte/char conversions are
+ * delayed and cached. Everything is recyclable.
+ *
+ * The object can represent a byte[], a char[], or a (sub) String. All
+ * operations can be made in case sensitive mode or not.
+ *
+ * @author dac@eng.sun.com
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Costin Manolache
+ */
+public final class MessageBytes implements Cloneable, Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // primary type ( whatever is set as original value )
+    private int type = T_NULL;
+
+    public static final int T_NULL = 0;
+    /** getType() is T_STR if the the object used to create the MessageBytes
+        was a String */
+    public static final int T_STR  = 1;
+    /** getType() is T_STR if the the object used to create the MessageBytes
+        was a byte[] */ 
+    public static final int T_BYTES = 2;
+    /** getType() is T_STR if the the object used to create the MessageBytes
+        was a char[] */ 
+    public static final int T_CHARS = 3;
+
+    private int hashCode=0;
+    // did we computed the hashcode ? 
+    private boolean hasHashCode=false;
+
+    // Internal objects to represent array + offset, and specific methods
+    private ByteChunk byteC=new ByteChunk();
+    private CharChunk charC=new CharChunk();
+    
+    // String
+    private String strValue;
+    // true if a String value was computed. Probably not needed,
+    // strValue!=null is the same
+    private boolean hasStrValue=false;
+
+    /**
+     * Creates a new, uninitialized MessageBytes object.
+     * Use static newInstance() in order to allow
+     *   future hooks.
+     */
+    private MessageBytes() {
+    }
+
+    /** Construct a new MessageBytes instance
+     */
+    public static MessageBytes newInstance() {
+        return factory.newInstance();
+    }
+
+    public MessageBytes getClone() {
+        try {
+            return (MessageBytes)this.clone();
+        } catch( Exception ex) {
+            return null;
+        }
+    }
+
+    public boolean isNull() {
+        // should we check also hasStrValue ???
+        return byteC.isNull() && charC.isNull() && ! hasStrValue;
+        // bytes==null && strValue==null;
+    }
+    
+    /**
+     * Resets the message bytes to an uninitialized (NULL) state.
+     */
+    public void recycle() {
+        type=T_NULL;
+        byteC.recycle();
+        charC.recycle();
+
+        strValue=null;
+
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=false;
+    hasLongValue=false;
+    }
+
+
+    /**
+     * Sets the content to the specified subarray of bytes.
+     *
+     * @param b the bytes
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     */
+    public void setBytes(byte[] b, int off, int len) {
+        byteC.setBytes( b, off, len );
+        type=T_BYTES;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=false;
+    }
+
+    /** Set the encoding. If the object was constructed from bytes[]. any
+     *  previous conversion is reset.
+     *  If no encoding is set, we'll use 8859-1.
+     */
+    public void setEncoding( String enc ) {
+        if( !byteC.isNull() ) {
+            // if the encoding changes we need to reset the conversion results
+            charC.recycle();
+            hasStrValue=false;
+        }
+        byteC.setEncoding(enc);
+    }
+
+    /** 
+     * Sets the content to be a char[]
+     *
+     * @param c the bytes
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     */
+    public void setChars( char[] c, int off, int len ) {
+        charC.setChars( c, off, len );
+        type=T_CHARS;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=false;
+    }
+
+    /** 
+     * Set the content to be a string
+     */
+    public void setString( String s ) {
+        strValue=s;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=false;
+        if (s == null) {
+            hasStrValue=false;
+            type=T_NULL;
+        } else {
+            hasStrValue=true;
+            type=T_STR;
+        }
+    }
+
+    // -------------------- Conversion and getters --------------------
+
+    /** Compute the string value
+     */
+    @Override
+    public String toString() {
+        if( hasStrValue ) return strValue;
+        
+        switch (type) {
+        case T_CHARS:
+            strValue=charC.toString();
+            hasStrValue=true;
+            return strValue;
+        case T_BYTES:
+            strValue=byteC.toString();
+            hasStrValue=true;
+            return strValue;
+        }
+        return null;
+    }
+
+    //----------------------------------------
+    /** Return the type of the original content. Can be
+     *  T_STR, T_BYTES, T_CHARS or T_NULL
+     */
+    public int getType() {
+        return type;
+    }
+    
+    /**
+     * Returns the byte chunk, representing the byte[] and offset/length.
+     * Valid only if T_BYTES or after a conversion was made.
+     */
+    public ByteChunk getByteChunk() {
+        return byteC;
+    }
+
+    /**
+     * Returns the char chunk, representing the char[] and offset/length.
+     * Valid only if T_CHARS or after a conversion was made.
+     */
+    public CharChunk getCharChunk() {
+        return charC;
+    }
+
+    /**
+     * Returns the string value.
+     * Valid only if T_STR or after a conversion was made.
+     */
+    public String getString() {
+        return strValue;
+    }
+
+    /** Do a char->byte conversion.
+     */
+    public void toBytes() {
+        if( ! byteC.isNull() ) {
+            type=T_BYTES;
+            return;
+        }
+        toString();
+        type=T_BYTES;
+        byte bb[] = strValue.getBytes();
+        byteC.setBytes(bb, 0, bb.length);
+    }
+
+    /** Convert to char[] and fill the CharChunk.
+     *  XXX Not optimized - it converts to String first.
+     */
+    public void toChars() {
+        if( ! charC.isNull() ) {
+            type=T_CHARS;
+            return;
+        }
+        // inefficient
+        toString();
+        type=T_CHARS;
+        char cc[]=strValue.toCharArray();
+        charC.setChars(cc, 0, cc.length);
+    }
+    
+
+    /**
+     * Returns the length of the original buffer.
+     * Note that the length in bytes may be different from the length
+     * in chars.
+     */
+    public int getLength() {
+        if(type==T_BYTES)
+            return byteC.getLength();
+        if(type==T_CHARS) {
+            return charC.getLength();
+        }
+        if(type==T_STR)
+            return strValue.length();
+        toString();
+        if( strValue==null ) return 0;
+        return strValue.length();
+    }
+
+    // -------------------- equals --------------------
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equals(String s) {
+        switch (type) {
+        case T_STR:
+            if (strValue == null) return s == null;
+            return strValue.equals( s );
+        case T_CHARS:
+            return charC.equals( s );
+        case T_BYTES:
+            return byteC.equals( s );
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Compares the message bytes to the specified String object.
+     * @param s the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equalsIgnoreCase(String s) {
+        switch (type) {
+        case T_STR:
+            if (strValue == null) return s == null;
+            return strValue.equalsIgnoreCase( s );
+        case T_CHARS:
+            return charC.equalsIgnoreCase( s );
+        case T_BYTES:
+            return byteC.equalsIgnoreCase( s );
+        default:
+            return false;
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof MessageBytes) {
+            return equals((MessageBytes) obj);
+        }
+        return false;
+    }
+
+    public boolean equals(MessageBytes mb) {
+        switch (type) {
+        case T_STR:
+            return mb.equals( strValue );
+        }
+
+        if( mb.type != T_CHARS &&
+            mb.type!= T_BYTES ) {
+            // it's a string or int/date string value
+            return equals( mb.toString() );
+        }
+
+        // mb is either CHARS or BYTES.
+        // this is either CHARS or BYTES
+        // Deal with the 4 cases ( in fact 3, one is symmetric)
+        
+        if( mb.type == T_CHARS && type==T_CHARS ) {
+            return charC.equals( mb.charC );
+        } 
+        if( mb.type==T_BYTES && type== T_BYTES ) {
+            return byteC.equals( mb.byteC );
+        }
+        if( mb.type== T_CHARS && type== T_BYTES ) {
+            return byteC.equals( mb.charC );
+        }
+        if( mb.type== T_BYTES && type== T_CHARS ) {
+            return mb.byteC.equals( charC );
+        }
+        // can't happen
+        return true;
+    }
+
+    
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     */
+    public boolean startsWith(String s) {
+        switch (type) {
+        case T_STR:
+            return strValue.startsWith( s );
+        case T_CHARS:
+            return charC.startsWith( s );
+        case T_BYTES:
+            return byteC.startsWith( s );
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param s the string
+     * @param pos The start position
+     */
+    public boolean startsWithIgnoreCase(String s, int pos) {
+        switch (type) {
+        case T_STR:
+            if( strValue==null ) return false;
+            if( strValue.length() < pos + s.length() ) return false;
+            
+            for( int i=0; i<s.length(); i++ ) {
+                if( Ascii.toLower( s.charAt( i ) ) !=
+                    Ascii.toLower( strValue.charAt( pos + i ))) {
+                    return false;
+                }
+            }
+            return true;
+        case T_CHARS:
+            return charC.startsWithIgnoreCase( s, pos );
+        case T_BYTES:
+            return byteC.startsWithIgnoreCase( s, pos );
+        default:
+            return false;
+        }
+    }
+
+
+    // -------------------- Hash code  --------------------
+    @Override
+    public  int hashCode() {
+        if( hasHashCode ) return hashCode;
+        int code = 0;
+
+        code=hash(); 
+        hashCode=code;
+        hasHashCode=true;
+        return code;
+    }
+
+    // normal hash. 
+    private int hash() {
+        int code=0;
+        switch (type) {
+        case T_STR:
+            // We need to use the same hash function
+            for (int i = 0; i < strValue.length(); i++) {
+                code = code * 37 + strValue.charAt( i );
+            }
+            return code;
+        case T_CHARS:
+            return charC.hash();
+        case T_BYTES:
+            return byteC.hash();
+        default:
+            return 0;
+        }
+    }
+
+    public int indexOf(char c) {
+        return indexOf( c, 0);
+    }
+
+    // Inefficient initial implementation. Will be replaced on the next
+    // round of tune-up
+    public int indexOf(String s, int starting) {
+        toString();
+        return strValue.indexOf( s, starting );
+    }
+    
+    // Inefficient initial implementation. Will be replaced on the next
+    // round of tune-up
+    public int indexOf(String s) {
+        return indexOf( s, 0 );
+    }
+    
+    public int indexOfIgnoreCase(String s, int starting) {
+        toString();
+        String upper=strValue.toUpperCase(Locale.ENGLISH);
+        String sU=s.toUpperCase(Locale.ENGLISH);
+        return upper.indexOf( sU, starting );
+    }
+    
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     * @param c the character
+     * @param starting The start position
+     */
+    public int indexOf(char c, int starting) {
+        switch (type) {
+        case T_STR:
+            return strValue.indexOf( c, starting );
+        case T_CHARS:
+            return charC.indexOf( c, starting);
+        case T_BYTES:
+            return byteC.indexOf( c, starting );
+        default:
+            return -1;
+        }
+    }
+
+    /** Copy the src into this MessageBytes, allocating more space if
+     *  needed
+     */
+    public void duplicate( MessageBytes src ) throws IOException
+    {
+        switch( src.getType() ) {
+        case MessageBytes.T_BYTES:
+            type=T_BYTES;
+            ByteChunk bc=src.getByteChunk();
+            byteC.allocate( 2 * bc.getLength(), -1 );
+            byteC.append( bc );
+            break;
+        case MessageBytes.T_CHARS:
+            type=T_CHARS;
+            CharChunk cc=src.getCharChunk();
+            charC.allocate( 2 * cc.getLength(), -1 );
+            charC.append( cc );
+            break;
+        case MessageBytes.T_STR:
+            type=T_STR;
+            String sc=src.getString();
+            this.setString( sc );
+            break;
+        }
+    }
+
+    // -------------------- Deprecated code --------------------
+    // efficient int, long and date
+    // XXX used only for headers - shouldn't be
+    // stored here.
+    private int intValue;
+    private boolean hasIntValue=false;
+    private long longValue;
+    private boolean hasLongValue=false;
+    
+    /** Set the buffer to the representation of an int
+     */
+    public void setInt(int i) {
+        byteC.allocate(16, 32);
+        int current = i;
+        byte[] buf = byteC.getBuffer();
+        int start = 0;
+        int end = 0;
+        if (i == 0) {
+            buf[end++] = (byte) '0';
+        }
+        if (i < 0) {
+            current = -i;
+            buf[end++] = (byte) '-';
+        }
+        while (current > 0) {
+            int digit = current % 10;
+            current = current / 10;
+            buf[end++] = HexUtils.getHex(digit);
+        }
+        byteC.setOffset(0);
+        byteC.setEnd(end);
+        // Inverting buffer
+        end--;
+        if (i < 0) {
+            start++;
+        }
+        while (end > start) {
+            byte temp = buf[start];
+            buf[start] = buf[end];
+            buf[end] = temp;
+            start++;
+            end--;
+        }
+        intValue=i;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=true;
+        hasLongValue=false;
+        type=T_BYTES;
+    }
+
+    /** Set the buffer to the representation of an long
+     */
+    public void setLong(long l) {
+        byteC.allocate(32, 64);
+        long current = l;
+        byte[] buf = byteC.getBuffer();
+        int start = 0;
+        int end = 0;
+        if (l == 0) {
+            buf[end++] = (byte) '0';
+        }
+        if (l < 0) {
+            current = -l;
+            buf[end++] = (byte) '-';
+        }
+        while (current > 0) {
+            int digit = (int) (current % 10);
+            current = current / 10;
+            buf[end++] = HexUtils.getHex(digit);
+        }
+        byteC.setOffset(0);
+        byteC.setEnd(end);
+        // Inverting buffer
+        end--;
+        if (l < 0) {
+            start++;
+        }
+        while (end > start) {
+            byte temp = buf[start];
+            buf[start] = buf[end];
+            buf[end] = temp;
+            start++;
+            end--;
+        }
+        longValue=l;
+        hasStrValue=false;
+        hasHashCode=false;
+        hasIntValue=false;
+        hasLongValue=true;
+        type=T_BYTES;
+    }
+
+    // Used for headers conversion
+    /** Convert the buffer to an int, cache the value
+     */ 
+    public int getInt() 
+    {
+        if( hasIntValue )
+            return intValue;
+        
+        switch (type) {
+        case T_BYTES:
+            intValue=byteC.getInt();
+            break;
+        default:
+            intValue=Integer.parseInt(toString());
+        }
+        hasIntValue=true;
+        return intValue;
+    }
+
+    // Used for headers conversion
+    /** Convert the buffer to an long, cache the value
+     */ 
+    public long getLong() {
+        if( hasLongValue )
+            return longValue;
+        
+        switch (type) {
+        case T_BYTES:
+            longValue=byteC.getLong();
+            break;
+        default:
+            longValue=Long.parseLong(toString());
+        }
+
+        hasLongValue=true;
+        return longValue;
+
+     }
+
+    // -------------------- Future may be different --------------------
+    
+    private static MessageBytesFactory factory=new MessageBytesFactory();
+
+    public static void setFactory( MessageBytesFactory mbf ) {
+        factory=mbf;
+    }
+    
+    public static class MessageBytesFactory {
+        protected MessageBytesFactory() {
+        }
+        public MessageBytes newInstance() {
+            return new MessageBytes();
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/StringCache.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/StringCache.java
new file mode 100644
index 0000000..23cd270
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/StringCache.java
@@ -0,0 +1,692 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * This class implements a String cache for ByteChunk and CharChunk.
+ *
+ * @author Remy Maucherat
+ */
+public class StringCache {
+
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( StringCache.class );
+    
+    
+    // ------------------------------------------------------- Static Variables
+
+    
+    /**
+     * Enabled ?
+     */
+    protected static boolean byteEnabled = ("true".equals(System.getProperty(
+            "tomcat.util.buf.StringCache.byte.enabled", "false")));
+
+    
+    protected static boolean charEnabled = ("true".equals(System.getProperty(
+            "tomcat.util.buf.StringCache.char.enabled", "false")));
+
+    
+    protected static int trainThreshold = Integer.parseInt(System.getProperty(
+            "tomcat.util.buf.StringCache.trainThreshold", "20000"));
+    
+
+    protected static int cacheSize = Integer.parseInt(System.getProperty(
+            "tomcat.util.buf.StringCache.cacheSize", "200"));
+    
+
+    protected static int maxStringSize = Integer.parseInt(System.getProperty(
+            "tomcat.util.buf.StringCache.maxStringSize", "128"));
+    
+
+   /**
+     * Statistics hash map for byte chunk.
+     */
+    protected static HashMap<ByteEntry,int[]> bcStats =
+        new HashMap<ByteEntry,int[]>(cacheSize);
+
+    
+    /**
+     * toString count for byte chunk.
+     */
+    protected static int bcCount = 0;
+    
+    
+    /**
+     * Cache for byte chunk.
+     */
+    protected static ByteEntry[] bcCache = null;
+    
+
+    /**
+     * Statistics hash map for char chunk.
+     */
+    protected static HashMap<CharEntry,int[]> ccStats =
+        new HashMap<CharEntry,int[]>(cacheSize);
+
+
+    /**
+     * toString count for char chunk.
+     */
+    protected static int ccCount = 0; 
+    
+
+    /**
+     * Cache for char chunk.
+     */
+    protected static CharEntry[] ccCache = null;
+
+    
+    /**
+     * Access count.
+     */
+    protected static int accessCount = 0;
+    
+
+    /**
+     * Hit count.
+     */
+    protected static int hitCount = 0;
+    
+
+    // ------------------------------------------------------------ Properties
+
+    
+    /**
+     * @return Returns the cacheSize.
+     */
+    public int getCacheSize() {
+        return cacheSize;
+    }
+    
+    
+    /**
+     * @param cacheSize The cacheSize to set.
+     */
+    public void setCacheSize(int cacheSize) {
+        StringCache.cacheSize = cacheSize;
+    }
+
+    
+    /**
+     * @return Returns the enabled.
+     */
+    public boolean getByteEnabled() {
+        return byteEnabled;
+    }
+    
+    
+    /**
+     * @param byteEnabled The enabled to set.
+     */
+    public void setByteEnabled(boolean byteEnabled) {
+        StringCache.byteEnabled = byteEnabled;
+    }
+    
+    
+    /**
+     * @return Returns the enabled.
+     */
+    public boolean getCharEnabled() {
+        return charEnabled;
+    }
+    
+    
+    /**
+     * @param charEnabled The enabled to set.
+     */
+    public void setCharEnabled(boolean charEnabled) {
+        StringCache.charEnabled = charEnabled;
+    }
+    
+    
+    /**
+     * @return Returns the trainThreshold.
+     */
+    public int getTrainThreshold() {
+        return trainThreshold;
+    }
+    
+    
+    /**
+     * @param trainThreshold The trainThreshold to set.
+     */
+    public void setTrainThreshold(int trainThreshold) {
+        StringCache.trainThreshold = trainThreshold;
+    }
+
+    
+    /**
+     * @return Returns the accessCount.
+     */
+    public int getAccessCount() {
+        return accessCount;
+    }
+    
+    
+    /**
+     * @return Returns the hitCount.
+     */
+    public int getHitCount() {
+        return hitCount;
+    }
+
+    
+    // -------------------------------------------------- Public Static Methods
+
+    
+    public void reset() {
+        hitCount = 0;
+        accessCount = 0;
+        synchronized (bcStats) {
+            bcCache = null;
+            bcCount = 0;
+        }
+        synchronized (ccStats) {
+            ccCache = null;
+            ccCount = 0;
+        }
+    }
+    
+    
+    public static String toString(ByteChunk bc) {
+
+        // If the cache is null, then either caching is disabled, or we're
+        // still training
+        if (bcCache == null) {
+            String value = bc.toStringInternal();
+            if (byteEnabled && (value.length() < maxStringSize)) {
+                // If training, everything is synced
+                synchronized (bcStats) {
+                    // If the cache has been generated on a previous invocation
+                    // while waiting for the lock, just return the toString
+                    // value we just calculated
+                    if (bcCache != null) {
+                        return value;
+                    }
+                    // Two cases: either we just exceeded the train count, in
+                    // which case the cache must be created, or we just update
+                    // the count for the string
+                    if (bcCount > trainThreshold) {
+                        long t1 = System.currentTimeMillis();
+                        // Sort the entries according to occurrence
+                        TreeMap<Integer,ArrayList<ByteEntry>> tempMap =
+                            new TreeMap<Integer,ArrayList<ByteEntry>>();
+                        for (Entry<ByteEntry,int[]> item : bcStats.entrySet()) {
+                            ByteEntry entry = item.getKey();
+                            int[] countA = item.getValue();
+                            Integer count = Integer.valueOf(countA[0]);
+                            // Add to the list for that count
+                            ArrayList<ByteEntry> list = tempMap.get(count);
+                            if (list == null) {
+                                // Create list
+                                list = new ArrayList<ByteEntry>();
+                                tempMap.put(count, list);
+                            }
+                            list.add(entry);
+                        }
+                        // Allocate array of the right size
+                        int size = bcStats.size();
+                        if (size > cacheSize) {
+                            size = cacheSize;
+                        }
+                        ByteEntry[] tempbcCache = new ByteEntry[size];
+                        // Fill it up using an alphabetical order
+                        // and a dumb insert sort
+                        ByteChunk tempChunk = new ByteChunk();
+                        int n = 0;
+                        while (n < size) {
+                            Object key = tempMap.lastKey();
+                            ArrayList<ByteEntry> list = tempMap.get(key);
+                            for (int i = 0; i < list.size() && n < size; i++) {
+                                ByteEntry entry = list.get(i);
+                                tempChunk.setBytes(entry.name, 0,
+                                        entry.name.length);
+                                int insertPos = findClosest(tempChunk,
+                                        tempbcCache, n);
+                                if (insertPos == n) {
+                                    tempbcCache[n + 1] = entry;
+                                } else {
+                                    System.arraycopy(tempbcCache, insertPos + 1,
+                                            tempbcCache, insertPos + 2,
+                                            n - insertPos - 1);
+                                    tempbcCache[insertPos + 1] = entry;
+                                }
+                                n++;
+                            }
+                            tempMap.remove(key);
+                        }
+                        bcCount = 0;
+                        bcStats.clear();
+                        bcCache = tempbcCache;
+                        if (log.isDebugEnabled()) {
+                            long t2 = System.currentTimeMillis();
+                            log.debug("ByteCache generation time: " +
+                                    (t2 - t1) + "ms");
+                        }
+                    } else {
+                        bcCount++;
+                        // Allocate new ByteEntry for the lookup
+                        ByteEntry entry = new ByteEntry();
+                        entry.value = value;
+                        int[] count = bcStats.get(entry);
+                        if (count == null) {
+                            int end = bc.getEnd();
+                            int start = bc.getStart();
+                            // Create byte array and copy bytes
+                            entry.name = new byte[bc.getLength()];
+                            System.arraycopy(bc.getBuffer(), start, entry.name,
+                                    0, end - start);
+                            // Set encoding
+                            entry.enc = bc.getEncoding();
+                            // Initialize occurrence count to one 
+                            count = new int[1];
+                            count[0] = 1;
+                            // Set in the stats hash map
+                            bcStats.put(entry, count);
+                        } else {
+                            count[0] = count[0] + 1;
+                        }
+                    }
+                }
+            }
+            return value;
+        } else {
+            accessCount++;
+            // Find the corresponding String
+            String result = find(bc);
+            if (result == null) {
+                return bc.toStringInternal();
+            }
+            // Note: We don't care about safety for the stats
+            hitCount++;
+            return result;
+        }
+        
+    }
+
+
+    public static String toString(CharChunk cc) {
+        
+        // If the cache is null, then either caching is disabled, or we're
+        // still training
+        if (ccCache == null) {
+            String value = cc.toStringInternal();
+            if (charEnabled && (value.length() < maxStringSize)) {
+                // If training, everything is synced
+                synchronized (ccStats) {
+                    // If the cache has been generated on a previous invocation
+                    // while waiting for the lock, just return the toString
+                    // value we just calculated
+                    if (ccCache != null) {
+                        return value;
+                    }
+                    // Two cases: either we just exceeded the train count, in
+                    // which case the cache must be created, or we just update
+                    // the count for the string
+                    if (ccCount > trainThreshold) {
+                        long t1 = System.currentTimeMillis();
+                        // Sort the entries according to occurrence
+                        TreeMap<Integer,ArrayList<CharEntry>> tempMap =
+                            new TreeMap<Integer,ArrayList<CharEntry>>();
+                        for (Entry<CharEntry,int[]> item : ccStats.entrySet()) {
+                            CharEntry entry = item.getKey();
+                            int[] countA = item.getValue();
+                            Integer count = Integer.valueOf(countA[0]);
+                            // Add to the list for that count
+                            ArrayList<CharEntry> list = tempMap.get(count);
+                            if (list == null) {
+                                // Create list
+                                list = new ArrayList<CharEntry>();
+                                tempMap.put(count, list);
+                            }
+                            list.add(entry);
+                        }
+                        // Allocate array of the right size
+                        int size = ccStats.size();
+                        if (size > cacheSize) {
+                            size = cacheSize;
+                        }
+                        CharEntry[] tempccCache = new CharEntry[size];
+                        // Fill it up using an alphabetical order
+                        // and a dumb insert sort
+                        CharChunk tempChunk = new CharChunk();
+                        int n = 0;
+                        while (n < size) {
+                            Object key = tempMap.lastKey();
+                            ArrayList<CharEntry> list = tempMap.get(key);
+                            for (int i = 0; i < list.size() && n < size; i++) {
+                                CharEntry entry = list.get(i);
+                                tempChunk.setChars(entry.name, 0,
+                                        entry.name.length);
+                                int insertPos = findClosest(tempChunk,
+                                        tempccCache, n);
+                                if (insertPos == n) {
+                                    tempccCache[n + 1] = entry;
+                                } else {
+                                    System.arraycopy(tempccCache, insertPos + 1,
+                                            tempccCache, insertPos + 2,
+                                            n - insertPos - 1);
+                                    tempccCache[insertPos + 1] = entry;
+                                }
+                                n++;
+                            }
+                            tempMap.remove(key);
+                        }
+                        ccCount = 0;
+                        ccStats.clear();
+                        ccCache = tempccCache;
+                        if (log.isDebugEnabled()) {
+                            long t2 = System.currentTimeMillis();
+                            log.debug("CharCache generation time: " +
+                                    (t2 - t1) + "ms");
+                        }
+                    } else {
+                        ccCount++;
+                        // Allocate new CharEntry for the lookup
+                        CharEntry entry = new CharEntry();
+                        entry.value = value;
+                        int[] count = ccStats.get(entry);
+                        if (count == null) {
+                            int end = cc.getEnd();
+                            int start = cc.getStart();
+                            // Create char array and copy chars
+                            entry.name = new char[cc.getLength()];
+                            System.arraycopy(cc.getBuffer(), start, entry.name,
+                                    0, end - start);
+                            // Initialize occurrence count to one 
+                            count = new int[1];
+                            count[0] = 1;
+                            // Set in the stats hash map
+                            ccStats.put(entry, count);
+                        } else {
+                            count[0] = count[0] + 1;
+                        }
+                    }
+                }
+            }
+            return value;
+        } else {
+            accessCount++;
+            // Find the corresponding String
+            String result = find(cc);
+            if (result == null) {
+                return cc.toStringInternal();
+            }
+            // Note: We don't care about safety for the stats
+            hitCount++;
+            return result;
+        }
+        
+    }
+    
+    
+    // ----------------------------------------------------- Protected Methods
+
+
+    /**
+     * Compare given byte chunk with byte array.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    protected static final int compare(ByteChunk name, byte[] compareTo) {
+        int result = 0;
+
+        byte[] b = name.getBuffer();
+        int start = name.getStart();
+        int end = name.getEnd();
+        int len = compareTo.length;
+
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (b[i + start] > compareTo[i]) {
+                result = 1;
+            } else if (b[i + start] < compareTo[i]) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length > (end - start)) {
+                result = -1;
+            } else if (compareTo.length < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+    
+    /**
+     * Find an entry given its name in the cache and return the associated
+     * String.
+     */
+    protected static final String find(ByteChunk name) {
+        int pos = findClosest(name, bcCache, bcCache.length);
+        if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
+                || !(name.getEncoding().equals(bcCache[pos].enc))) {
+            return null;
+        } else {
+            return bcCache[pos].value;
+        }
+    }
+
+    
+    /**
+     * Find an entry given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    protected static final int findClosest(ByteChunk name, ByteEntry[] array,
+            int len) {
+
+        int a = 0;
+        int b = len - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (compare(name, array[0].name) < 0) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) >>> 1;
+            int result = compare(name, array[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compare(name, array[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Compare given char chunk with char array.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    protected static final int compare(CharChunk name, char[] compareTo) {
+        int result = 0;
+
+        char[] c = name.getBuffer();
+        int start = name.getStart();
+        int end = name.getEnd();
+        int len = compareTo.length;
+
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (c[i + start] > compareTo[i]) {
+                result = 1;
+            } else if (c[i + start] < compareTo[i]) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length > (end - start)) {
+                result = -1;
+            } else if (compareTo.length < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+    
+    /**
+     * Find an entry given its name in the cache and return the associated
+     * String.
+     */
+    protected static final String find(CharChunk name) {
+        int pos = findClosest(name, ccCache, ccCache.length);
+        if ((pos < 0) || (compare(name, ccCache[pos].name) != 0)) {
+            return null;
+        } else {
+            return ccCache[pos].value;
+        }
+    }
+
+    
+    /**
+     * Find an entry given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    protected static final int findClosest(CharChunk name, CharEntry[] array,
+            int len) {
+
+        int a = 0;
+        int b = len - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (compare(name, array[0].name) < 0 ) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) >>> 1;
+            int result = compare(name, array[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compare(name, array[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    // -------------------------------------------------- ByteEntry Inner Class
+
+
+    public static class ByteEntry {
+
+        public byte[] name = null;
+        public String enc = null;
+        public String value = null;
+
+        @Override
+        public String toString() {
+            return value;
+        }
+        @Override
+        public int hashCode() {
+            return value.hashCode();
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ByteEntry) {
+                return value.equals(((ByteEntry) obj).value);
+            }
+            return false;
+        }
+        
+    }
+
+
+    // -------------------------------------------------- CharEntry Inner Class
+
+
+    public static class CharEntry {
+
+        public char[] name = null;
+        public String value = null;
+
+        @Override
+        public String toString() {
+            return value;
+        }
+        @Override
+        public int hashCode() {
+            return value.hashCode();
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof CharEntry) {
+                return value.equals(((CharEntry) obj).value);
+            }
+            return false;
+        }
+        
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/UDecoder.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/UDecoder.java
new file mode 100644
index 0000000..381125c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/UDecoder.java
@@ -0,0 +1,276 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+import java.io.CharConversionException;
+import java.io.IOException;
+
+/** 
+ *  All URL decoding happens here. This way we can reuse, review, optimize
+ *  without adding complexity to the buffers.
+ *
+ *  The conversion will modify the original buffer.
+ * 
+ *  @author Costin Manolache
+ */
+public final class UDecoder {
+    
+    protected static final boolean ALLOW_ENCODED_SLASH = 
+        Boolean.valueOf(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false")).booleanValue();
+
+    public UDecoder() 
+    {
+    }
+
+    /** URLDecode, will modify the source.  Includes converting
+     *  '+' to ' '.
+     */
+    public void convert( ByteChunk mb )
+        throws IOException
+    {
+        convert(mb, true);
+    }
+
+    /** URLDecode, will modify the source.
+     */
+    public void convert( ByteChunk mb, boolean query )
+        throws IOException
+    {
+        int start=mb.getOffset();
+        byte buff[]=mb.getBytes();
+        int end=mb.getEnd();
+
+        int idx= ByteChunk.indexOf( buff, start, end, '%' );
+        int idx2=-1;
+        if( query )
+            idx2= ByteChunk.indexOf( buff, start, end, '+' );
+        if( idx<0 && idx2<0 ) {
+            return;
+        }
+
+        // idx will be the smallest positive indexes ( first % or + )
+        if( idx2 >= 0 && idx2 < idx ) idx=idx2;
+        if( idx < 0 ) idx=idx2;
+
+        boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
+    
+        for( int j=idx; j<end; j++, idx++ ) {
+            if( buff[ j ] == '+' && query) {
+                buff[idx]= (byte)' ' ;
+            } else if( buff[ j ] != '%' ) {
+                buff[idx]= buff[j];
+            } else {
+                // read next 2 digits
+                if( j+2 >= end ) {
+                    throw new CharConversionException("EOF");
+                }
+                byte b1= buff[j+1];
+                byte b2=buff[j+2];
+                if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
+                    throw new CharConversionException( "isHexDigit");
+                
+                j+=2;
+                int res=x2c( b1, b2 );
+                if (noSlash && (res == '/')) {
+                    throw new CharConversionException( "noSlash");
+                }
+                buff[idx]=(byte)res;
+            }
+        }
+
+        mb.setEnd( idx );
+        
+        return;
+    }
+
+    // -------------------- Additional methods --------------------
+    // XXX What do we do about charset ????
+
+    /** In-buffer processing - the buffer will be modified
+     *  Includes converting  '+' to ' '.
+     */
+    public void convert( CharChunk mb )
+        throws IOException
+    {
+        convert(mb, true);
+    }
+
+    /** In-buffer processing - the buffer will be modified
+     */
+    public void convert( CharChunk mb, boolean query )
+        throws IOException
+    {
+        //        log( "Converting a char chunk ");
+        int start=mb.getOffset();
+        char buff[]=mb.getBuffer();
+        int cend=mb.getEnd();
+
+        int idx= CharChunk.indexOf( buff, start, cend, '%' );
+        int idx2=-1;
+        if( query )
+            idx2= CharChunk.indexOf( buff, start, cend, '+' );
+        if( idx<0 && idx2<0 ) {
+            return;
+        }
+        
+        if( idx2 >= 0 && idx2 < idx ) idx=idx2; 
+        if( idx < 0 ) idx=idx2;
+
+        for( int j=idx; j<cend; j++, idx++ ) {
+            if( buff[ j ] == '+' && query ) {
+                buff[idx]=( ' ' );
+            } else if( buff[ j ] != '%' ) {
+                buff[idx]=buff[j];
+            } else {
+                // read next 2 digits
+                if( j+2 >= cend ) {
+                    // invalid
+                    throw new CharConversionException("EOF");
+                }
+                char b1= buff[j+1];
+                char b2=buff[j+2];
+                if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
+                    throw new CharConversionException("isHexDigit");
+                
+                j+=2;
+                int res=x2c( b1, b2 );
+                buff[idx]=(char)res;
+            }
+        }
+        mb.setEnd( idx );
+    }
+
+    /** URLDecode, will modify the source
+     *  Includes converting  '+' to ' '.
+     */
+    public void convert(MessageBytes mb)
+        throws IOException
+    {
+        convert(mb, true);
+    }
+
+    /** URLDecode, will modify the source
+     */
+    public void convert(MessageBytes mb, boolean query)
+        throws IOException
+    {
+        
+        switch (mb.getType()) {
+        case MessageBytes.T_STR:
+            String strValue=mb.toString();
+            if( strValue==null ) return;
+            mb.setString( convert( strValue, query ));
+            break;
+        case MessageBytes.T_CHARS:
+            CharChunk charC=mb.getCharChunk();
+            convert( charC, query );
+            break;
+        case MessageBytes.T_BYTES:
+            ByteChunk bytesC=mb.getByteChunk();
+            convert( bytesC, query );
+            break;
+        }
+    }
+
+    // XXX Old code, needs to be replaced !!!!
+    // 
+    public final String convert(String str)
+    {
+        return convert(str, true);
+    }
+
+    public final String convert(String str, boolean query)
+    {
+        if (str == null)  return  null;
+        
+        if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 )
+            return str;
+        
+        StringBuilder dec = new StringBuilder();    // decoded string output
+        int strPos = 0;
+        int strLen = str.length();
+
+        dec.ensureCapacity(str.length());
+        while (strPos < strLen) {
+            int laPos;        // lookahead position
+
+            // look ahead to next URLencoded metacharacter, if any
+            for (laPos = strPos; laPos < strLen; laPos++) {
+                char laChar = str.charAt(laPos);
+                if ((laChar == '+' && query) || (laChar == '%')) {
+                    break;
+                }
+            }
+
+            // if there were non-metacharacters, copy them all as a block
+            if (laPos > strPos) {
+                dec.append(str.substring(strPos,laPos));
+                strPos = laPos;
+            }
+
+            // shortcut out of here if we're at the end of the string
+            if (strPos >= strLen) {
+                break;
+            }
+
+            // process next metacharacter
+            char metaChar = str.charAt(strPos);
+            if (metaChar == '+') {
+                dec.append(' ');
+                strPos++;
+                continue;
+            } else if (metaChar == '%') {
+                // We throw the original exception - the super will deal with
+                // it
+                //                try {
+                dec.append((char)Integer.
+                           parseInt(str.substring(strPos + 1, strPos + 3),16));
+                strPos += 3;
+            }
+        }
+
+        return dec.toString();
+    }
+
+
+
+    private static boolean isHexDigit( int c ) {
+        return ( ( c>='0' && c<='9' ) ||
+                 ( c>='a' && c<='f' ) ||
+                 ( c>='A' && c<='F' ));
+    }
+    
+    private static int x2c( byte b1, byte b2 ) {
+        int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
+            (b1 -'0');
+        digit*=16;
+        digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
+            (b2 -'0');
+        return digit;
+    }
+
+    private static int x2c( char b1, char b2 ) {
+        int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
+            (b1 -'0');
+        digit*=16;
+        digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
+            (b2 -'0');
+        return digit;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/UEncoder.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/UEncoder.java
new file mode 100644
index 0000000..ba30b7f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/UEncoder.java
@@ -0,0 +1,174 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.buf;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.BitSet;
+
+/** Efficient implementation for encoders.
+ *  This class is not thread safe - you need one encoder per thread.
+ *  The encoder will save and recycle the internal objects, avoiding
+ *  garbage.
+ * 
+ *  You can add extra characters that you want preserved, for example
+ *  while encoding a URL you can add "/".
+ *
+ *  @author Costin Manolache
+ */
+public final class UEncoder {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(UEncoder.class );
+    
+    // Not static - the set may differ ( it's better than adding
+    // an extra check for "/", "+", etc
+    private BitSet safeChars=null;
+    private C2BConverter c2b=null;
+    private ByteChunk bb=null;
+
+    private String encoding="UTF8";
+    
+    public UEncoder() {
+        initSafeChars();
+    }
+
+    public void setEncoding( String s ) {
+        encoding=s;
+    }
+
+    public void addSafeCharacter( char c ) {
+        safeChars.set( c );
+    }
+
+
+    /** URL Encode string, using a specified encoding.
+     *
+     * @param buf The writer
+     * @param s string to be encoded
+     * @throws IOException If an I/O error occurs
+     */
+    public void urlEncode( Writer buf, String s )
+            throws IOException {
+        if( c2b==null ) {
+            bb=new ByteChunk(16); // small enough.
+            c2b=new C2BConverter( bb, encoding );
+        }
+
+        for (int i = 0; i < s.length(); i++) {
+            int c = s.charAt(i);
+            if( safeChars.get( c ) ) {
+                if(log.isDebugEnabled())
+                    log.debug("Encoder: Safe: " + (char)c);
+                buf.write((char)c);
+            } else {
+                if(log.isDebugEnabled())
+                    log.debug("Encoder: Unsafe:  " + (char)c);
+                c2b.convert( (char)c );
+                
+                // "surrogate" - UTF is _not_ 16 bit, but 21 !!!!
+                // ( while UCS is 31 ). Amazing...
+                if (c >= 0xD800 && c <= 0xDBFF) {
+                    if ( (i+1) < s.length()) {
+                        int d = s.charAt(i+1);
+                        if (d >= 0xDC00 && d <= 0xDFFF) {
+                            if(log.isDebugEnabled())
+                                log.debug("Encoder: Unsafe:  " + c);
+                            c2b.convert( (char)d);
+                            i++;
+                        }
+                    }
+                }
+
+                c2b.flushBuffer();
+                
+                urlEncode( buf, bb.getBuffer(), bb.getOffset(),
+                           bb.getLength() );
+                bb.recycle();
+            }
+        }
+    }
+
+    /**
+     */
+    public void urlEncode( Writer buf, byte bytes[], int off, int len)
+            throws IOException {
+        for( int j=off; j< len; j++ ) {
+            buf.write( '%' );
+            char ch = Character.forDigit((bytes[j] >> 4) & 0xF, 16);
+            if(log.isDebugEnabled())
+                log.debug("Encoder: Encode:  " + ch);
+            buf.write(ch);
+            ch = Character.forDigit(bytes[j] & 0xF, 16);
+            if(log.isDebugEnabled())
+                log.debug("Encoder: Encode:  " + ch);
+            buf.write(ch);
+        }
+    }
+    
+    /**
+     * Utility function to re-encode the URL.
+     * Still has problems with charset, since UEncoder mostly
+     * ignores it.
+     */
+    public String encodeURL(String uri) {
+        String outUri=null;
+        try {
+            // XXX optimize - recycle, etc
+            CharArrayWriter out = new CharArrayWriter();
+            urlEncode(out, uri);
+            outUri=out.toString();
+        } catch (IOException iex) {
+        }
+        return outUri;
+    }
+    
+
+    // -------------------- Internal implementation --------------------
+    
+    private void initSafeChars() {
+        safeChars=new BitSet(128);
+        int i;
+        for (i = 'a'; i <= 'z'; i++) {
+            safeChars.set(i);
+        }
+        for (i = 'A'; i <= 'Z'; i++) {
+            safeChars.set(i);
+        }
+        for (i = '0'; i <= '9'; i++) {
+            safeChars.set(i);
+        }
+        //safe
+        safeChars.set('$');
+        safeChars.set('-');
+        safeChars.set('_');
+        safeChars.set('.');
+
+        // Dangerous: someone may treat this as " "
+        // RFC1738 does allow it, it's not reserved
+        //    safeChars.set('+');
+        //extra
+        safeChars.set('!');
+        safeChars.set('*');
+        safeChars.set('\'');
+        safeChars.set('(');
+        safeChars.set(')');
+        safeChars.set(',');
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/package.html
new file mode 100644
index 0000000..e0da4da
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/package.html
@@ -0,0 +1,38 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<html><body>
+<H1>Buffers and Encodings</h1>
+
+This package contains buffers and utils to perform encoding/decoding of buffers. That includes byte to char
+conversions, URL encodings, etc. 
+
+<p>
+Encoding is a critical operation for performance. There are few tricks in this package - the C2B and 
+B2C converters are caching a ISReader/OSWriter and keep everything allocated to do the conversions 
+in any VM without any garbage.
+
+<p>
+This package must accomodate future extensions and additional converters ( most imporant: the nio.charset, 
+which should be detected and used if available ). Also, we do have one hand-written UTF8Decoder, and 
+other tuned encoders could be added.
+
+<p>
+My benchmarks ( I'm costin :-) show only small differences between C2B, B2C and hand-written codders/decoders,
+so UTF8Decoder may be disabled. 
+
+<p>
+</body></html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/Constants.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/Constants.java
new file mode 100644
index 0000000..a056e6c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.util.file;
+
+/**
+ * String constants for the file package.
+ */
+public final class Constants {
+
+    public static final String Package = "org.apache.tomcat.util.file";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/LocalStrings.properties
new file mode 100644
index 0000000..eff0251
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/LocalStrings.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+matcher.tokenize=Tokenizing path [{0}]
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/Matcher.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/Matcher.java
new file mode 100644
index 0000000..2f9265b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/Matcher.java
@@ -0,0 +1,566 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.tomcat.util.file;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * <p>This is a utility class to match file globs.
+ * The class has been derived from
+ * org.apache.tools.ant.types.selectors.SelectorUtils.
+ * </p>
+ * <p>All methods are static.</p>
+ */
+public final class Matcher {
+
+    /**
+     * The pattern that matches an arbitrary number of directories.
+     */
+    public static final String DEEP_TREE_MATCH = "**";
+
+    private static final String OS_NAME =
+        System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
+    private static final String PATH_SEP =
+        System.getProperty("path.separator");
+    private static final boolean ON_NETWARE = isNetware();
+    private static final boolean ON_DOS = isDos();
+
+    /**
+     * The string resources for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private static final Log log = LogFactory.getLog(Matcher.class);
+
+    /**
+     * Tests whether or not a given path matches any pattern in the given set.
+     *
+     * If you need to call this method multiple times with the same 
+     * pattern you should rather pre parse the pattern using tokenizePathAsArray.
+     *
+     * @see #tokenizePathAsArray
+     * 
+     * @param patternSet The pattern set to match against. Must not be
+     *                <code>null</code>.
+     * @param str     The path to match, as a String. Must not be
+     *                <code>null</code>.
+     *
+     * @return <code>true</code> if any pattern in the set matches against the string,
+     *         or <code>false</code> otherwise.
+     */
+    public static boolean matchPath(Set<String[]> patternSet, String str) {
+        for (String[] patternTokens: patternSet) {
+            if (matchPath(patternTokens, tokenizePathAsArray(str), true)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Tests whether or not a given path matches a given pattern.
+     *
+     * If you need to call this method multiple times with the same 
+     * pattern you should rather pre parse the pattern using tokenizePathAsArray.
+     *
+     * @see #tokenizePathAsArray
+     * 
+     * @param pattern The pattern to match against. Must not be
+     *                <code>null</code>.
+     * @param str     The path to match, as a String. Must not be
+     *                <code>null</code>.
+     *
+     * @return <code>true</code> if the pattern matches against the string,
+     *         or <code>false</code> otherwise.
+     */
+    public static boolean matchPath(String pattern, String str) {
+        String[] patDirs = tokenizePathAsArray(pattern);
+        return matchPath(patDirs, tokenizePathAsArray(str), true);
+    }
+
+    /**
+     * Tests whether or not a given path matches a given pattern.
+     * 
+     * If you need to call this method multiple times with the same 
+     * pattern you should rather pre parse the pattern using tokenizePathAsArray.
+     *
+     * @see #tokenizePathAsArray
+     * 
+     * @param pattern The pattern to match against. Must not be
+     *                <code>null</code>.
+     * @param str     The path to match, as a String. Must not be
+     *                <code>null</code>.
+     * @param isCaseSensitive Whether or not matching should be performed
+     *                        case sensitively.
+     *
+     * @return <code>true</code> if the pattern matches against the string,
+     *         or <code>false</code> otherwise.
+     */
+    public static boolean matchPath(String pattern, String str,
+                                    boolean isCaseSensitive) {
+        String[] patDirs = tokenizePathAsArray(pattern);
+        return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive);
+    }
+
+    /**
+     * Core implementation of matchPath using an already tokenized pattern.
+     */
+    public static boolean matchPath(String[] tokenizedPattern, String[] strDirs,
+                             boolean isCaseSensitive) {
+        int patIdxStart = 0;
+        int patIdxEnd = tokenizedPattern.length - 1;
+        int strIdxStart = 0;
+        int strIdxEnd = strDirs.length - 1;
+
+        // up to first '**'
+        while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+            String patDir = tokenizedPattern[patIdxStart];
+            if (patDir.equals(DEEP_TREE_MATCH)) {
+                break;
+            }
+            if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
+                return false;
+            }
+            patIdxStart++;
+            strIdxStart++;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // String is exhausted
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            if (patIdxStart > patIdxEnd) {
+                // String not exhausted, but pattern is. Failure.
+                return false;
+            }
+        }
+
+        // up to last '**'
+        while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+            String patDir = tokenizedPattern[patIdxEnd];
+            if (patDir.equals(DEEP_TREE_MATCH)) {
+                break;
+            }
+            if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) {
+                return false;
+            }
+            patIdxEnd--;
+            strIdxEnd--;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // String is exhausted
+            for (int i = patIdxStart; i <= patIdxEnd; i++) {
+                if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+            int patIdxTmp = -1;
+            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+                if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
+                    patIdxTmp = i;
+                    break;
+                }
+            }
+            if (patIdxTmp == patIdxStart + 1) {
+                // '**/**' situation, so skip one
+                patIdxStart++;
+                continue;
+            }
+            // Find the pattern between padIdxStart & padIdxTmp in str between
+            // strIdxStart & strIdxEnd
+            int patLength = (patIdxTmp - patIdxStart - 1);
+            int strLength = (strIdxEnd - strIdxStart + 1);
+            int foundIdx = -1;
+            strLoop:
+                        for (int i = 0; i <= strLength - patLength; i++) {
+                            for (int j = 0; j < patLength; j++) {
+                                String subPat = tokenizedPattern[patIdxStart + j + 1];
+                                String subStr = strDirs[strIdxStart + i + j];
+                                if (!match(subPat, subStr, isCaseSensitive)) {
+                                    continue strLoop;
+                                }
+                            }
+
+                            foundIdx = strIdxStart + i;
+                            break;
+                        }
+
+            if (foundIdx == -1) {
+                return false;
+            }
+
+            patIdxStart = patIdxTmp;
+            strIdxStart = foundIdx + patLength;
+        }
+
+        for (int i = patIdxStart; i <= patIdxEnd; i++) {
+            if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Tests whether or not a string matches against a pattern.
+     * The pattern may contain two special characters:<br>
+     * '*' means zero or more characters<br>
+     * '?' means one and only one character
+     *
+     * @param pattern The pattern to match against.
+     *                Must not be <code>null</code>.
+     * @param str     The string which must be matched against the pattern.
+     *                Must not be <code>null</code>.
+     *
+     * @return <code>true</code> if the string matches against the pattern,
+     *         or <code>false</code> otherwise.
+     */
+    public static boolean match(String pattern, String str) {
+        return match(pattern, str, true);
+    }
+
+    /**
+     * Tests whether or not a string matches against a pattern.
+     * The pattern may contain two special characters:<br>
+     * '*' means zero or more characters<br>
+     * '?' means one and only one character
+     *
+     * @param pattern The pattern to match against.
+     *                Must not be <code>null</code>.
+     * @param str     The string which must be matched against the pattern.
+     *                Must not be <code>null</code>.
+     * @param caseSensitive Whether or not matching should be performed
+     *                        case sensitively.
+     *
+     *
+     * @return <code>true</code> if the string matches against the pattern,
+     *         or <code>false</code> otherwise.
+     */
+    public static boolean match(String pattern, String str,
+                                boolean caseSensitive) {
+        char[] patArr = pattern.toCharArray();
+        char[] strArr = str.toCharArray();
+        int patIdxStart = 0;
+        int patIdxEnd = patArr.length - 1;
+        int strIdxStart = 0;
+        int strIdxEnd = strArr.length - 1;
+        char ch;
+
+        boolean containsStar = false;
+        for (int i = 0; i < patArr.length; i++) {
+            if (patArr[i] == '*') {
+                containsStar = true;
+                break;
+            }
+        }
+
+        if (!containsStar) {
+            // No '*'s, so we make a shortcut
+            if (patIdxEnd != strIdxEnd) {
+                return false; // Pattern and string do not have the same size
+            }
+            for (int i = 0; i <= patIdxEnd; i++) {
+                ch = patArr[i];
+                if (ch != '?') {
+                    if (different(caseSensitive, ch, strArr[i])) {
+                        return false; // Character mismatch
+                    }
+                }
+            }
+            return true; // String matches against pattern
+        }
+
+        if (patIdxEnd == 0) {
+            return true; // Pattern contains only '*', which matches anything
+        }
+
+        // Process characters before first star
+        while (true) {
+            ch = patArr[patIdxStart];
+            if (ch == '*' || strIdxStart > strIdxEnd) {
+                break;
+            }
+            if (ch != '?') {
+                if (different(caseSensitive, ch, strArr[strIdxStart])) {
+                    return false; // Character mismatch
+                }
+            }
+            patIdxStart++;
+            strIdxStart++;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            return allStars(patArr, patIdxStart, patIdxEnd);
+        }
+
+        // Process characters after last star
+        while (true) {
+            ch = patArr[patIdxEnd];
+            if (ch == '*' || strIdxStart > strIdxEnd) {
+                break;
+            }
+            if (ch != '?') {
+                if (different(caseSensitive, ch, strArr[strIdxEnd])) {
+                    return false; // Character mismatch
+                }
+            }
+            patIdxEnd--;
+            strIdxEnd--;
+        }
+        if (strIdxStart > strIdxEnd) {
+            // All characters in the string are used. Check if only '*'s are
+            // left in the pattern. If so, we succeeded. Otherwise failure.
+            return allStars(patArr, patIdxStart, patIdxEnd);
+        }
+
+        // process pattern between stars. padIdxStart and patIdxEnd point
+        // always to a '*'.
+        while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+            int patIdxTmp = -1;
+            for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+                if (patArr[i] == '*') {
+                    patIdxTmp = i;
+                    break;
+                }
+            }
+            if (patIdxTmp == patIdxStart + 1) {
+                // Two stars next to each other, skip the first one.
+                patIdxStart++;
+                continue;
+            }
+            // Find the pattern between padIdxStart & padIdxTmp in str between
+            // strIdxStart & strIdxEnd
+            int patLength = (patIdxTmp - patIdxStart - 1);
+            int strLength = (strIdxEnd - strIdxStart + 1);
+            int foundIdx = -1;
+            strLoop:
+            for (int i = 0; i <= strLength - patLength; i++) {
+                for (int j = 0; j < patLength; j++) {
+                    ch = patArr[patIdxStart + j + 1];
+                    if (ch != '?') {
+                        if (different(caseSensitive, ch,
+                                      strArr[strIdxStart + i + j])) {
+                            continue strLoop;
+                        }
+                    }
+                }
+
+                foundIdx = strIdxStart + i;
+                break;
+            }
+
+            if (foundIdx == -1) {
+                return false;
+            }
+
+            patIdxStart = patIdxTmp;
+            strIdxStart = foundIdx + patLength;
+        }
+
+        // All characters in the string are used. Check if only '*'s are left
+        // in the pattern. If so, we succeeded. Otherwise failure.
+        return allStars(patArr, patIdxStart, patIdxEnd);
+    }
+
+    private static boolean allStars(char[] chars, int start, int end) {
+        for (int i = start; i <= end; ++i) {
+            if (chars[i] != '*') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean different(
+        boolean caseSensitive, char ch, char other) {
+        return caseSensitive
+            ? ch != other
+            : Character.toUpperCase(ch) != Character.toUpperCase(other);
+    }
+
+    /**
+     * Breaks a path up into a array of path elements, tokenizing on
+     * <code>File.separator</code>.
+     *
+     * @param path Path to tokenize. Must not be <code>null</code>.
+     *
+     * @return a String array of path elements from the tokenized path
+     */
+    public static String[] tokenizePathAsArray(String path) {
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("matcher.tokenize", path));
+        }
+        String root = null;
+        if (isAbsolutePath(path)) {
+            String[] s = dissect(path);
+            root = s[0];
+            path = s[1];
+        }
+        char sep = File.separatorChar;
+        int start = 0;
+        int len = path.length();
+        int count = 0;
+        for (int pos = 0; pos < len; pos++) {
+            if (path.charAt(pos) == sep) {
+                if (pos != start) {
+                    count++;
+                }
+                start = pos + 1;
+            }
+        }
+        if (len != start) {
+            count++;
+        }
+        String[] l = new String[count + ((root == null) ? 0 : 1)];
+
+        if (root != null) {
+            l[0] = root;
+            count = 1;
+        } else {
+            count = 0;
+        }
+        start = 0;
+        for (int pos = 0; pos < len; pos++) {
+            if (path.charAt(pos) == sep) {
+                if (pos != start) {
+                    String tok = path.substring(start, pos);
+                    l[count++] = tok;
+                }
+                start = pos + 1;
+            }
+        }
+        if (len != start) {
+            String tok = path.substring(start);
+            l[count/*++*/] = tok;
+        }
+        return l;
+    }
+
+    /**
+     * Dissect the specified absolute path.
+     * @param path the path to dissect (must be absolute).
+     * @return String[] {root, remaining path}.
+     * @throws java.lang.NullPointerException if path is null.
+     */
+    private static String[] dissect(String path) {
+        char sep = File.separatorChar;
+        path = path.replace('/', sep).replace('\\', sep);
+
+        String root = null;
+        int colon = path.indexOf(':');
+        if (colon > 0 && (ON_DOS || ON_NETWARE)) {
+
+            int next = colon + 1;
+            root = path.substring(0, next);
+            char[] ca = path.toCharArray();
+            root += sep;
+            //remove the initial separator; the root has it.
+            next = (ca[next] == sep) ? next + 1 : next;
+
+            StringBuilder sbPath = new StringBuilder();
+            // Eliminate consecutive slashes after the drive spec:
+            for (int i = next; i < ca.length; i++) {
+                if (ca[i] != sep || ca[i - 1] != sep) {
+                    sbPath.append(ca[i]);
+                }
+            }
+            path = sbPath.toString();
+        } else if (path.length() > 1 && path.charAt(1) == sep) {
+            // UNC drive
+            int nextsep = path.indexOf(sep, 2);
+            nextsep = path.indexOf(sep, nextsep + 1);
+            root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path;
+            path = path.substring(root.length());
+        } else {
+            root = File.separator;
+            path = path.substring(1);
+        }
+        return new String[] {root, path};
+    }
+
+    /**
+     * Verifies that the specified filename represents an absolute path.
+     * Differs from new java.io.File("filename").isAbsolute() in that a path
+     * beginning with a double file separator--signifying a Windows UNC--must
+     * at minimum match "\\a\b" to be considered an absolute path.
+     * @param filename the filename to be checked.
+     * @return true if the filename represents an absolute path.
+     * @throws java.lang.NullPointerException if filename is null.
+     */
+    private static boolean isAbsolutePath(String filename) {
+        int len = filename.length();
+        if (len == 0) {
+            return false;
+        }
+        char sep = File.separatorChar;
+        filename = filename.replace('/', sep).replace('\\', sep);
+        char c = filename.charAt(0);
+        if (!(ON_DOS || ON_NETWARE)) {
+            return (c == sep);
+        }
+        if (c == sep) {
+            // CheckStyle:MagicNumber OFF
+            if (!(ON_DOS && len > 4 && filename.charAt(1) == sep)) {
+                return false;
+            }
+            // CheckStyle:MagicNumber ON
+            int nextsep = filename.indexOf(sep, 2);
+            return nextsep > 2 && nextsep + 1 < len;
+        }
+        int colon = filename.indexOf(':');
+        return (Character.isLetter(c) && colon == 1
+                && filename.length() > 2 && filename.charAt(2) == sep)
+                || (ON_NETWARE && colon > 0);
+    }
+
+    /**
+     * Determines if our OS is Netware.
+     *
+     * @return true if we run on Netware
+     */
+    private static boolean isNetware() {
+        return OS_NAME.indexOf("netware") > -1;
+    }
+
+    /**
+     * Determines if our OS is DOS.
+     *
+     * @return true if we run on DOS
+     */
+    private static boolean isDos() {
+        return PATH_SEP.equals(";") && !isNetware();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/package.html
new file mode 100644
index 0000000..900fb4b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/file/package.html
@@ -0,0 +1,29 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+$Id: package.html,v 1.1 2011/06/28 21:14:17 rherrmann Exp $
+-->
+</head>
+<body bgcolor="white">
+<p>
+This package contains utility classes for file handling.
+</p>
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
new file mode 100644
index 0000000..88f204a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
@@ -0,0 +1,743 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.disk;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+
+import org.apache.tomcat.util.http.fileupload.DeferredFileOutputStream;
+import org.apache.tomcat.util.http.fileupload.FileItem;
+import org.apache.tomcat.util.http.fileupload.FileItemHeaders;
+import org.apache.tomcat.util.http.fileupload.FileItemHeadersSupport;
+import org.apache.tomcat.util.http.fileupload.FileUploadException;
+import org.apache.tomcat.util.http.fileupload.IOUtils;
+import org.apache.tomcat.util.http.fileupload.ParameterParser;
+import org.apache.tomcat.util.http.fileupload.util.Streams;
+
+
+/**
+ * <p> The default implementation of the
+ * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
+ *
+ * <p> After retrieving an instance of this class from a {@link
+ * org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} instance (see
+ * {@link org.apache.tomcat.util.http.fileupload.FileUpload
+ * #parseRequest(org.apache.tomcat.util.http.fileupload.RequestContext)}), you
+ * may either request all contents of file at once using {@link #get()} or
+ * request an {@link java.io.InputStream InputStream} with
+ * {@link #getInputStream()} and process the file without attempting to load
+ * it into memory, which may come handy with large files.
+ *
+ * <p>Temporary files, which are created for file items, should be
+ * deleted later on. The best way to do this is using a
+ * {@link org.apache.tomcat.util.http.fileupload.FileCleaningTracker
+ * FileCleaningTracker}, which you can set on the
+ * {@link DiskFileItemFactory}. However, if you do use such a tracker,
+ * then you must consider the following: Temporary files are automatically
+ * deleted as soon as they are no longer needed. (More precisely, when the
+ * corresponding instance of {@link java.io.File} is garbage collected.)
+ * This is done by the so-called reaper thread, which is started
+ * automatically when the class {@link org.apache.commons.io.FileCleaner}
+ * is loaded.
+ * It might make sense to terminate that thread, for example, if
+ * your web application ends. See the section on "Resource cleanup"
+ * in the users guide of commons-fileupload.</p>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id: DiskFileItem.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ */
+public class DiskFileItem
+    implements FileItem, FileItemHeadersSupport {
+
+    // ----------------------------------------------------- Manifest constants
+
+    /**
+     * The UID to use when serializing this instance.
+     */
+    private static final long serialVersionUID = 2237570099615271025L;
+
+
+    /**
+     * Default content charset to be used when no explicit charset
+     * parameter is provided by the sender. Media subtypes of the
+     * "text" type are defined to have a default charset value of
+     * "ISO-8859-1" when received via HTTP.
+     */
+    public static final String DEFAULT_CHARSET = "ISO-8859-1";
+
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * UID used in unique file name generation.
+     */
+    private static final String UID =
+            new java.rmi.server.UID().toString()
+                .replace(':', '_').replace('-', '_');
+
+    /**
+     * Counter used in unique identifier generation.
+     */
+    private static int counter = 0;
+
+
+    /**
+     * The name of the form field as provided by the browser.
+     */
+    private String fieldName;
+
+
+    /**
+     * The content type passed by the browser, or <code>null</code> if
+     * not defined.
+     */
+    private String contentType;
+
+
+    /**
+     * Whether or not this item is a simple form field.
+     */
+    private boolean isFormField;
+
+
+    /**
+     * The original filename in the user's filesystem.
+     */
+    private String fileName;
+
+
+    /**
+     * The size of the item, in bytes. This is used to cache the size when a
+     * file item is moved from its original location.
+     */
+    private long size = -1;
+
+
+    /**
+     * The threshold above which uploads will be stored on disk.
+     */
+    private int sizeThreshold;
+
+
+    /**
+     * The directory in which uploaded files will be stored, if stored on disk.
+     */
+    private File repository;
+
+
+    /**
+     * Cached contents of the file.
+     */
+    private byte[] cachedContent;
+
+
+    /**
+     * Output stream for this item.
+     */
+    private transient DeferredFileOutputStream dfos;
+
+    /**
+     * The temporary file to use.
+     */
+    private transient File tempFile;
+
+    /**
+     * File to allow for serialization of the content of this item.
+     */
+    private File dfosFile;
+
+    /**
+     * The file items headers.
+     */
+    private FileItemHeaders headers;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs a new <code>DiskFileItem</code> instance.
+     *
+     * @param fieldName     The name of the form field.
+     * @param contentType   The content type passed by the browser or
+     *                      <code>null</code> if not specified.
+     * @param isFormField   Whether or not this item is a plain form field, as
+     *                      opposed to a file upload.
+     * @param fileName      The original filename in the user's filesystem, or
+     *                      <code>null</code> if not specified.
+     * @param sizeThreshold The threshold, in bytes, below which items will be
+     *                      retained in memory and above which they will be
+     *                      stored as a file.
+     * @param repository    The data repository, which is the directory in
+     *                      which files will be created, should the item size
+     *                      exceed the threshold.
+     */
+    public DiskFileItem(String fieldName,
+            String contentType, boolean isFormField, String fileName,
+            int sizeThreshold, File repository) {
+        this.fieldName = fieldName;
+        this.contentType = contentType;
+        this.isFormField = isFormField;
+        this.fileName = fileName;
+        this.sizeThreshold = sizeThreshold;
+        this.repository = repository;
+    }
+
+
+    // ------------------------------- Methods from javax.activation.DataSource
+
+
+    /**
+     * Returns an {@link java.io.InputStream InputStream} that can be
+     * used to retrieve the contents of the file.
+     *
+     * @return An {@link java.io.InputStream InputStream} that can be
+     *         used to retrieve the contents of the file.
+     *
+     * @throws IOException if an error occurs.
+     */
+    public InputStream getInputStream()
+        throws IOException {
+        if (!isInMemory()) {
+            return new FileInputStream(dfos.getFile());
+        }
+
+        if (cachedContent == null) {
+            cachedContent = dfos.getData();
+        }
+        return new ByteArrayInputStream(cachedContent);
+    }
+
+
+    /**
+     * Returns the content type passed by the agent or <code>null</code> if
+     * not defined.
+     *
+     * @return The content type passed by the agent or <code>null</code> if
+     *         not defined.
+     */
+    public String getContentType() {
+        return contentType;
+    }
+
+
+    /**
+     * Returns the content charset passed by the agent or <code>null</code> if
+     * not defined.
+     *
+     * @return The content charset passed by the agent or <code>null</code> if
+     *         not defined.
+     */
+    public String getCharSet() {
+        ParameterParser parser = new ParameterParser();
+        parser.setLowerCaseNames(true);
+        // Parameter parser can handle null input
+        Map<String,String> params = parser.parse(getContentType(), ';');
+        return params.get("charset");
+    }
+
+
+    /**
+     * Returns the original filename in the client's filesystem.
+     *
+     * @return The original filename in the client's filesystem.
+     * @throws org.apache.tomcat.util.http.fileupload.InvalidFileNameException
+     *   The file name contains a NUL character, which might be an indicator of
+     *   a security attack. If you intend to use the file name anyways, catch
+     *   the exception and use InvalidFileNameException#getName().
+     */
+    public String getName() {
+        return Streams.checkFileName(fileName);
+    }
+
+
+    // ------------------------------------------------------- FileItem methods
+
+
+    /**
+     * Provides a hint as to whether or not the file contents will be read
+     * from memory.
+     *
+     * @return <code>true</code> if the file contents will be read
+     *         from memory; <code>false</code> otherwise.
+     */
+    public boolean isInMemory() {
+        if (cachedContent != null) {
+            return true;
+        }
+        return dfos.isInMemory();
+    }
+
+
+    /**
+     * Returns the size of the file.
+     *
+     * @return The size of the file, in bytes.
+     */
+    public long getSize() {
+        if (size >= 0) {
+            return size;
+        } else if (cachedContent != null) {
+            return cachedContent.length;
+        } else if (dfos.isInMemory()) {
+            return dfos.getData().length;
+        } else {
+            return dfos.getFile().length();
+        }
+    }
+
+
+    /**
+     * Returns the contents of the file as an array of bytes.  If the
+     * contents of the file were not yet cached in memory, they will be
+     * loaded from the disk storage and cached.
+     *
+     * @return The contents of the file as an array of bytes.
+     */
+    public byte[] get() {
+        if (isInMemory()) {
+            if (cachedContent == null) {
+                cachedContent = dfos.getData();
+            }
+            return cachedContent;
+        }
+
+        byte[] fileData = new byte[(int) getSize()];
+        FileInputStream fis = null;
+
+        try {
+            fis = new FileInputStream(dfos.getFile());
+            fis.read(fileData);
+        } catch (IOException e) {
+            fileData = null;
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+
+        return fileData;
+    }
+
+
+    /**
+     * Returns the contents of the file as a String, using the specified
+     * encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the file.
+     *
+     * @param charset The charset to use.
+     *
+     * @return The contents of the file, as a string.
+     *
+     * @throws UnsupportedEncodingException if the requested character
+     *                                      encoding is not available.
+     */
+    public String getString(final String charset)
+        throws UnsupportedEncodingException {
+        return new String(get(), charset);
+    }
+
+
+    /**
+     * Returns the contents of the file as a String, using the default
+     * character encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the file.
+     *
+     * @return The contents of the file, as a string.
+     *
+     * TODO Consider making this method throw UnsupportedEncodingException.
+     */
+    public String getString() {
+        byte[] rawdata = get();
+        String charset = getCharSet();
+        if (charset == null) {
+            charset = DEFAULT_CHARSET;
+        }
+        try {
+            return new String(rawdata, charset);
+        } catch (UnsupportedEncodingException e) {
+            return new String(rawdata);
+        }
+    }
+
+
+    /**
+     * A convenience method to write an uploaded item to disk. The client code
+     * is not concerned with whether or not the item is stored in memory, or on
+     * disk in a temporary location. They just want to write the uploaded item
+     * to a file.
+     * <p>
+     * This implementation first attempts to rename the uploaded item to the
+     * specified destination file, if the item was originally written to disk.
+     * Otherwise, the data will be copied to the specified file.
+     * <p>
+     * This method is only guaranteed to work <em>once</em>, the first time it
+     * is invoked for a particular item. This is because, in the event that the
+     * method renames a temporary file, that file will no longer be available
+     * to copy or rename again at a later time.
+     *
+     * @param file The <code>File</code> into which the uploaded item should
+     *             be stored.
+     *
+     * @throws Exception if an error occurs.
+     */
+    public void write(File file) throws Exception {
+        if (isInMemory()) {
+            FileOutputStream fout = null;
+            try {
+                fout = new FileOutputStream(file);
+                fout.write(get());
+            } finally {
+                if (fout != null) {
+                    fout.close();
+                }
+            }
+        } else {
+            File outputFile = getStoreLocation();
+            if (outputFile != null) {
+                // Save the length of the file
+                size = outputFile.length();
+                /*
+                 * The uploaded file is being stored on disk
+                 * in a temporary location so move it to the
+                 * desired file.
+                 */
+                if (!outputFile.renameTo(file)) {
+                    BufferedInputStream in = null;
+                    BufferedOutputStream out = null;
+                    try {
+                        in = new BufferedInputStream(
+                            new FileInputStream(outputFile));
+                        out = new BufferedOutputStream(
+                                new FileOutputStream(file));
+                        IOUtils.copy(in, out);
+                    } finally {
+                        if (in != null) {
+                            try {
+                                in.close();
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                        }
+                        if (out != null) {
+                            try {
+                                out.close();
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                        }
+                    }
+                }
+            } else {
+                /*
+                 * For whatever reason we cannot write the
+                 * file to disk.
+                 */
+                throw new FileUploadException(
+                    "Cannot write uploaded file to disk!");
+            }
+        }
+    }
+
+
+    /**
+     * Deletes the underlying storage for a file item, including deleting any
+     * associated temporary disk file. Although this storage will be deleted
+     * automatically when the <code>FileItem</code> instance is garbage
+     * collected, this method can be used to ensure that this is done at an
+     * earlier time, thus preserving system resources.
+     */
+    public void delete() {
+        cachedContent = null;
+        File outputFile = getStoreLocation();
+        if (outputFile != null && outputFile.exists()) {
+            outputFile.delete();
+        }
+    }
+
+
+    /**
+     * Returns the name of the field in the multipart form corresponding to
+     * this file item.
+     *
+     * @return The name of the form field.
+     *
+     * @see #setFieldName(java.lang.String)
+     *
+     */
+    public String getFieldName() {
+        return fieldName;
+    }
+
+
+    /**
+     * Sets the field name used to reference this file item.
+     *
+     * @param fieldName The name of the form field.
+     *
+     * @see #getFieldName()
+     *
+     */
+    public void setFieldName(String fieldName) {
+        this.fieldName = fieldName;
+    }
+
+
+    /**
+     * Determines whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @return <code>true</code> if the instance represents a simple form
+     *         field; <code>false</code> if it represents an uploaded file.
+     *
+     * @see #setFormField(boolean)
+     *
+     */
+    public boolean isFormField() {
+        return isFormField;
+    }
+
+
+    /**
+     * Specifies whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @param state <code>true</code> if the instance represents a simple form
+     *              field; <code>false</code> if it represents an uploaded file.
+     *
+     * @see #isFormField()
+     *
+     */
+    public void setFormField(boolean state) {
+        isFormField = state;
+    }
+
+
+    /**
+     * Returns an {@link java.io.OutputStream OutputStream} that can
+     * be used for storing the contents of the file.
+     *
+     * @return An {@link java.io.OutputStream OutputStream} that can be used
+     *         for storing the contensts of the file.
+     *
+     * @throws IOException if an error occurs.
+     */
+    public OutputStream getOutputStream()
+        throws IOException {
+        if (dfos == null) {
+            File outputFile = getTempFile();
+            dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
+        }
+        return dfos;
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
+     * data's temporary location on the disk. Note that for
+     * <code>FileItem</code>s that have their data stored in memory,
+     * this method will return <code>null</code>. When handling large
+     * files, you can use {@link java.io.File#renameTo(java.io.File)} to
+     * move the file to new location without copying the data, if the
+     * source and destination locations reside within the same logical
+     * volume.
+     *
+     * @return The data file, or <code>null</code> if the data is stored in
+     *         memory.
+     */
+    public File getStoreLocation() {
+        return dfos == null ? null : dfos.getFile();
+    }
+
+
+    // ------------------------------------------------------ Protected methods
+
+
+    /**
+     * Removes the file contents from the temporary storage.
+     */
+    @Override
+    protected void finalize() {
+        File outputFile = dfos.getFile();
+
+        if (outputFile != null && outputFile.exists()) {
+            outputFile.delete();
+        }
+    }
+
+
+    /**
+     * Creates and returns a {@link java.io.File File} representing a uniquely
+     * named temporary file in the configured repository path. The lifetime of
+     * the file is tied to the lifetime of the <code>FileItem</code> instance;
+     * the file will be deleted when the instance is garbage collected.
+     *
+     * @return The {@link java.io.File File} to be used for temporary storage.
+     */
+    protected File getTempFile() {
+        if (tempFile == null) {
+            File tempDir = repository;
+            if (tempDir == null) {
+                tempDir = new File(System.getProperty("java.io.tmpdir"));
+            }
+
+            String tempFileName =
+                "upload_" + UID + "_" + getUniqueId() + ".tmp";
+
+            tempFile = new File(tempDir, tempFileName);
+        }
+        return tempFile;
+    }
+
+
+    // -------------------------------------------------------- Private methods
+
+
+    /**
+     * Returns an identifier that is unique within the class loader used to
+     * load this class, but does not have random-like apearance.
+     *
+     * @return A String with the non-random looking instance identifier.
+     */
+    private static String getUniqueId() {
+        final int limit = 100000000;
+        int current;
+        synchronized (DiskFileItem.class) {
+            current = counter++;
+        }
+        String id = Integer.toString(current);
+
+        // If you manage to get more than 100 million of ids, you'll
+        // start getting ids longer than 8 characters.
+        if (current < limit) {
+            id = ("00000000" + id).substring(id.length());
+        }
+        return id;
+    }
+
+
+    /**
+     * Returns a string representation of this object.
+     *
+     * @return a string representation of this object.
+     */
+    @Override
+    public String toString() {
+        return "name=" + this.getName()
+            + ", StoreLocation="
+            + String.valueOf(this.getStoreLocation())
+            + ", size="
+            + this.getSize()
+            + "bytes, "
+            + "isFormField=" + isFormField()
+            + ", FieldName="
+            + this.getFieldName();
+    }
+
+
+    // -------------------------------------------------- Serialization methods
+
+
+    /**
+     * Writes the state of this object during serialization.
+     *
+     * @param out The stream to which the state should be written.
+     *
+     * @throws IOException if an error occurs.
+     */
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        // Read the data
+        if (dfos.isInMemory()) {
+            cachedContent = get();
+        } else {
+            cachedContent = null;
+            dfosFile = dfos.getFile();
+        }
+
+        // write out values
+        out.defaultWriteObject();
+    }
+
+    /**
+     * Reads the state of this object during deserialization.
+     *
+     * @param in The stream from which the state should be read.
+     *
+     * @throws IOException if an error occurs.
+     * @throws ClassNotFoundException if class cannot be found.
+     */
+    private void readObject(ObjectInputStream in)
+            throws IOException, ClassNotFoundException {
+        // read values
+        in.defaultReadObject();
+
+        OutputStream output = getOutputStream();
+        if (cachedContent != null) {
+            output.write(cachedContent);
+        } else {
+            FileInputStream input = new FileInputStream(dfosFile);
+            IOUtils.copy(input, output);
+            dfosFile.delete();
+            dfosFile = null;
+        }
+        output.close();
+
+        cachedContent = null;
+    }
+
+    /**
+     * Returns the file item headers.
+     * @return The file items headers.
+     */
+    public FileItemHeaders getHeaders() {
+        return headers;
+    }
+
+    /**
+     * Sets the file item headers.
+     * @param pHeaders The file items headers.
+     */
+    public void setHeaders(FileItemHeaders pHeaders) {
+        headers = pHeaders;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
new file mode 100644
index 0000000..26bd13d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.disk;
+
+import java.io.File;
+
+import org.apache.tomcat.util.http.fileupload.FileCleaningTracker;
+import org.apache.tomcat.util.http.fileupload.FileItem;
+import org.apache.tomcat.util.http.fileupload.FileItemFactory;
+
+
+/**
+ * <p>The default {@link org.apache.tomcat.util.http.fileupload.FileItemFactory}
+ * implementation. This implementation creates
+ * {@link org.apache.tomcat.util.http.fileupload.FileItem} instances which keep
+ * their
+ * content either in memory, for smaller items, or in a temporary file on disk,
+ * for larger items. The size threshold, above which content will be stored on
+ * disk, is configurable, as is the directory in which temporary files will be
+ * created.</p>
+ *
+ * <p>If not otherwise configured, the default configuration values are as
+ * follows:
+ * <ul>
+ *   <li>Size threshold is 10KB.</li>
+ *   <li>Repository is the system default temp directory, as returned by
+ *       <code>System.getProperty("java.io.tmpdir")</code>.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>Temporary files, which are created for file items, should be
+ * deleted later on. The best way to do this is using a
+ * {@link FileCleaningTracker}, which you can set on the
+ * {@link DiskFileItemFactory}. However, if you do use such a tracker,
+ * then you must consider the following: Temporary files are automatically
+ * deleted as soon as they are no longer needed. (More precisely, when the
+ * corresponding instance of {@link java.io.File} is garbage collected.)
+ * This is done by the so-called reaper thread, which is started
+ * automatically when the class {@link org.apache.commons.io.FileCleaner}
+ * is loaded.
+ * It might make sense to terminate that thread, for example, if
+ * your web application ends. See the section on "Resource cleanup"
+ * in the users guide of commons-fileupload.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id: DiskFileItemFactory.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ */
+public class DiskFileItemFactory implements FileItemFactory {
+
+    // ----------------------------------------------------- Manifest constants
+
+
+    /**
+     * The default threshold above which uploads will be stored on disk.
+     */
+    public static final int DEFAULT_SIZE_THRESHOLD = 10240;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The directory in which uploaded files will be stored, if stored on disk.
+     */
+    private File repository;
+
+
+    /**
+     * The threshold above which uploads will be stored on disk.
+     */
+    private int sizeThreshold = DEFAULT_SIZE_THRESHOLD;
+
+
+    /**
+     * <p>The instance of {@link FileCleaningTracker}, which is responsible
+     * for deleting temporary files.</p>
+     * <p>May be null, if tracking files is not required.</p>
+     */
+    private FileCleaningTracker fileCleaningTracker;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an unconfigured instance of this class. The resulting factory
+     * may be configured by calling the appropriate setter methods.
+     */
+    public DiskFileItemFactory() {
+        this(DEFAULT_SIZE_THRESHOLD, null);
+    }
+
+
+    /**
+     * Constructs a preconfigured instance of this class.
+     *
+     * @param sizeThreshold The threshold, in bytes, below which items will be
+     *                      retained in memory and above which they will be
+     *                      stored as a file.
+     * @param repository    The data repository, which is the directory in
+     *                      which files will be created, should the item size
+     *                      exceed the threshold.
+     */
+    public DiskFileItemFactory(int sizeThreshold, File repository) {
+        this.sizeThreshold = sizeThreshold;
+        this.repository = repository;
+    }
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Returns the directory used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @return The directory in which temporary files will be located.
+     *
+     * @see #setRepository(java.io.File)
+     *
+     */
+    public File getRepository() {
+        return repository;
+    }
+
+
+    /**
+     * Sets the directory used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @param repository The directory in which temporary files will be located.
+     *
+     * @see #getRepository()
+     *
+     */
+    public void setRepository(File repository) {
+        this.repository = repository;
+    }
+
+
+    /**
+     * Returns the size threshold beyond which files are written directly to
+     * disk. The default value is 10240 bytes.
+     *
+     * @return The size threshold, in bytes.
+     *
+     * @see #setSizeThreshold(int)
+     */
+    public int getSizeThreshold() {
+        return sizeThreshold;
+    }
+
+
+    /**
+     * Sets the size threshold beyond which files are written directly to disk.
+     *
+     * @param sizeThreshold The size threshold, in bytes.
+     *
+     * @see #getSizeThreshold()
+     *
+     */
+    public void setSizeThreshold(int sizeThreshold) {
+        this.sizeThreshold = sizeThreshold;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Create a new {@link DiskFileItem}
+     * instance from the supplied parameters and the local factory
+     * configuration.
+     *
+     * @param fieldName   The name of the form field.
+     * @param contentType The content type of the form field.
+     * @param isFormField <code>true</code> if this is a plain form field;
+     *                    <code>false</code> otherwise.
+     * @param fileName    The name of the uploaded file, if any, as supplied
+     *                    by the browser or other client.
+     *
+     * @return The newly created file item.
+     */
+    public FileItem createItem(String fieldName, String contentType,
+            boolean isFormField, String fileName) {
+        DiskFileItem result = new DiskFileItem(fieldName, contentType,
+                isFormField, fileName, sizeThreshold, repository);
+        FileCleaningTracker tracker = getFileCleaningTracker();
+        if (tracker != null) {
+            tracker.track(result.getTempFile(), this);
+        }
+        return result;
+    }
+
+
+    /**
+     * Returns the tracker, which is responsible for deleting temporary
+     * files.
+     * @return An instance of {@link FileCleaningTracker}, or null
+     *   (default), if temporary files aren't tracked.
+     */
+    public FileCleaningTracker getFileCleaningTracker() {
+        return fileCleaningTracker;
+    }
+
+    /**
+     * Sets the tracker, which is responsible for deleting temporary
+     * files.
+     * @param pTracker An instance of {@link FileCleaningTracker},
+     *   which will from now on track the created files, or null
+     *   (default), to disable tracking.
+     */
+    public void setFileCleaningTracker(FileCleaningTracker pTracker) {
+        fileCleaningTracker = pTracker;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/servlet/ServletFileUpload.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/servlet/ServletFileUpload.java
new file mode 100644
index 0000000..d7774e8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/servlet/ServletFileUpload.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.servlet;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.tomcat.util.http.fileupload.FileItem;
+import org.apache.tomcat.util.http.fileupload.FileItemFactory;
+import org.apache.tomcat.util.http.fileupload.FileItemIterator;
+import org.apache.tomcat.util.http.fileupload.FileUpload;
+import org.apache.tomcat.util.http.fileupload.FileUploadException;
+
+
+/**
+ * <p>High level API for processing file uploads.</p>
+ *
+ * <p>This class handles multiple files per single HTML widget, sent using
+ * <code>multipart/mixed</code> encoding type, as specified by
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
+ * #parseRequest(HttpServletRequest)} to acquire a list of {@link
+ * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
+ * widget.</p>
+ *
+ * <p>How the data for individual parts is stored is determined by the factory
+ * used to create them; a given part may be in memory, on disk, or somewhere
+ * else.</p>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: ServletFileUpload.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ */
+public class ServletFileUpload extends FileUpload {
+
+    // ---------------------------------------------------------- Class methods
+
+
+    /**
+     * Utility method that determines whether the request contains multipart
+     * content.
+     *
+     * @param request The servlet request to be evaluated. Must be non-null.
+     *
+     * @return <code>true</code> if the request is multipart;
+     *         <code>false</code> otherwise.
+     */
+    public static final boolean isMultipartContent(
+            HttpServletRequest request) {
+        if (!"post".equals(request.getMethod().toLowerCase(Locale.ENGLISH))) {
+            return false;
+        }
+        String contentType = request.getContentType();
+        if (contentType == null) {
+            return false;
+        }
+        if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
+            return true;
+        }
+        return false;
+    }
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an uninitialised instance of this class. A factory must be
+     * configured, using <code>setFileItemFactory()</code>, before attempting
+     * to parse requests.
+     *
+     * @see FileUpload#FileUpload(FileItemFactory)
+     */
+    public ServletFileUpload() {
+        super();
+    }
+
+
+    /**
+     * Constructs an instance of this class which uses the supplied factory to
+     * create <code>FileItem</code> instances.
+     *
+     * @see FileUpload#FileUpload()
+     * @param fileItemFactory The factory to use for creating file items.
+     */
+    public ServletFileUpload(FileItemFactory fileItemFactory) {
+        super(fileItemFactory);
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param request The servlet request to be parsed.
+     *
+     * @return A list of <code>FileItem</code> instances parsed from the
+     *         request, in the order that they were transmitted.
+     *
+     * @throws FileUploadException if there are problems reading/parsing
+     *                             the request or storing files.
+     */
+    public List<FileItem> parseRequest(HttpServletRequest request)
+    throws FileUploadException {
+        return parseRequest(new ServletRequestContext(request));
+    }
+
+
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param request The servlet request to be parsed.
+     *
+     * @return An iterator to instances of <code>FileItemStream</code>
+     *         parsed from the request, in the order that they were
+     *         transmitted.
+     *
+     * @throws FileUploadException if there are problems reading/parsing
+     *                             the request or storing files.
+     * @throws IOException An I/O error occurred. This may be a network
+     *   error while communicating with the client or a problem while
+     *   storing the uploaded content.
+     */
+    public FileItemIterator getItemIterator(HttpServletRequest request)
+    throws FileUploadException, IOException {
+        return super.getItemIterator(new ServletRequestContext(request));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
new file mode 100644
index 0000000..928e8f3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.tomcat.util.http.fileupload.RequestContext;
+
+
+/**
+ * <p>Provides access to the request information needed for a request made to
+ * an HTTP servlet.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id: ServletRequestContext.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ */
+public class ServletRequestContext implements RequestContext {
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The request for which the context is being provided.
+     */
+    private HttpServletRequest request;
+
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a context for this request.
+     *
+     * @param request The request to which this context applies.
+     */
+    public ServletRequestContext(HttpServletRequest request) {
+        this.request = request;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Retrieve the character encoding for the request.
+     *
+     * @return The character encoding for the request.
+     */
+    public String getCharacterEncoding() {
+        return request.getCharacterEncoding();
+    }
+
+    /**
+     * Retrieve the content type of the request.
+     *
+     * @return The content type of the request.
+     */
+    public String getContentType() {
+        return request.getContentType();
+    }
+
+    /**
+     * Retrieve the content length of the request.
+     *
+     * @return The content length of the request.
+     */
+    public int getContentLength() {
+        return request.getContentLength();
+    }
+
+    /**
+     * Retrieve the input stream for the request.
+     *
+     * @return The input stream for the request.
+     *
+     * @throws IOException if a problem occurs.
+     */
+    public InputStream getInputStream() throws IOException {
+        return request.getInputStream();
+    }
+
+    /**
+     * Returns a string representation of this object.
+     *
+     * @return a string representation of this object.
+     */
+    @Override
+    public String toString() {
+        return "ContentLength="
+            + this.getContentLength()
+            + ", ContentType="
+            + this.getContentType();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/Closeable.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/Closeable.java
new file mode 100644
index 0000000..2c18c47
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/Closeable.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.util;
+
+import java.io.IOException;
+
+
+/**
+ * Interface of an object, which may be closed.
+ */
+public interface Closeable {
+    /**
+     * Closes the object.
+     * @throws IOException An I/O error occurred.
+     */
+    void close() throws IOException;
+
+    /**
+     * Returns, whether the object is already closed.
+     * @return True, if the object is closed, otherwise false.
+     * @throws IOException An I/O error occurred.
+     */
+    boolean isClosed() throws IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
new file mode 100644
index 0000000..72c8563
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.tomcat.util.http.fileupload.FileItemHeaders;
+
+
+/**
+ * Default implementation of the {@link FileItemHeaders} interface.
+ *
+ * @author Michael C. Macaluso
+ * @since 1.3
+ */
+public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
+    private static final long serialVersionUID = -4455695752627032559L;
+
+    /**
+     * Map of <code>String</code> keys to a <code>List</code> of
+     * <code>String</code> instances.
+     */
+    private final Map<String,List<String>> headerNameToValueListMap =
+        new HashMap<String,List<String>>();
+
+    /**
+     * List to preserve order of headers as added.  This would not be
+     * needed if a <code>LinkedHashMap</code> could be used, but don't
+     * want to depend on 1.4.
+     */
+    private final List<String> headerNameList = new ArrayList<String>();
+
+    public String getHeader(String name) {
+        String nameLower = name.toLowerCase(Locale.ENGLISH);
+        List<String> headerValueList = headerNameToValueListMap.get(nameLower);
+        if (null == headerValueList) {
+            return null;
+        }
+        return headerValueList.get(0);
+    }
+
+    public Iterator<String> getHeaderNames() {
+        return headerNameList.iterator();
+    }
+
+    public Iterator<String> getHeaders(String name) {
+        String nameLower = name.toLowerCase(Locale.ENGLISH);
+        List<String> headerValueList = headerNameToValueListMap.get(nameLower);
+        if (null == headerValueList) {
+            return Collections.<String>emptyList().iterator();
+        }
+        return headerValueList.iterator();
+    }
+
+    /**
+     * Method to add header values to this instance.
+     *
+     * @param name name of this header
+     * @param value value of this header
+     */
+    public synchronized void addHeader(String name, String value) {
+        String nameLower = name.toLowerCase();
+        List<String> headerValueList = headerNameToValueListMap.get(nameLower);
+        if (null == headerValueList) {
+            headerValueList = new ArrayList<String>();
+            headerNameToValueListMap.put(nameLower, headerValueList);
+            headerNameList.add(nameLower);
+        }
+        headerValueList.add(value);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
new file mode 100644
index 0000000..0f4a40e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.util;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * An input stream, which limits its data size. This stream is
+ * used, if the content length is unknown.
+ */
+public abstract class LimitedInputStream
+        extends FilterInputStream implements Closeable {
+    /**
+     * The maximum size of an item, in bytes.
+     */
+    private long sizeMax;
+    /**
+     * The current number of bytes.
+     */
+    private long count;
+    /**
+     * Whether this stream is already closed.
+     */
+    private boolean closed;
+
+    /**
+     * Creates a new instance.
+     * @param pIn The input stream, which shall be limited.
+     * @param pSizeMax The limit; no more than this number of bytes
+     *   shall be returned by the source stream.
+     */
+    public LimitedInputStream(InputStream pIn, long pSizeMax) {
+        super(pIn);
+        sizeMax = pSizeMax;
+    }
+
+    /**
+     * Called to indicate, that the input streams limit has
+     * been exceeded.
+     * @param pSizeMax The input streams limit, in bytes.
+     * @param pCount The actual number of bytes.
+     * @throws IOException The called method is expected
+     *   to raise an IOException.
+     */
+    protected abstract void raiseError(long pSizeMax, long pCount)
+            throws IOException;
+
+    /** Called to check, whether the input streams
+     * limit is reached.
+     * @throws IOException The given limit is exceeded.
+     */
+    private void checkLimit() throws IOException {
+        if (count > sizeMax) {
+            raiseError(sizeMax, count);
+        }
+    }
+
+    /**
+     * Reads the next byte of data from this input stream. The value
+     * byte is returned as an <code>int</code> in the range
+     * <code>0</code> to <code>255</code>. If no byte is available
+     * because the end of the stream has been reached, the value
+     * <code>-1</code> is returned. This method blocks until input data
+     * is available, the end of the stream is detected, or an exception
+     * is thrown.
+     * <p>
+     * This method
+     * simply performs <code>in.read()</code> and returns the result.
+     *
+     * @return     the next byte of data, or <code>-1</code> if the end of the
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    @Override
+    public int read() throws IOException {
+        int res = super.read();
+        if (res != -1) {
+            count++;
+            checkLimit();
+        }
+        return res;
+    }
+
+    /**
+     * Reads up to <code>len</code> bytes of data from this input stream
+     * into an array of bytes. If <code>len</code> is not zero, the method
+     * blocks until some input is available; otherwise, no
+     * bytes are read and <code>0</code> is returned.
+     * <p>
+     * This method simply performs <code>in.read(b, off, len)</code>
+     * and returns the result.
+     *
+     * @param      b     the buffer into which the data is read.
+     * @param      off   The start offset in the destination array
+     *                   <code>b</code>.
+     * @param      len   the maximum number of bytes read.
+     * @return     the total number of bytes read into the buffer, or
+     *             <code>-1</code> if there is no more data because the end of
+     *             the stream has been reached.
+     * @exception  NullPointerException If <code>b</code> is <code>null</code>.
+     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
+     * <code>len</code> is negative, or <code>len</code> is greater than
+     * <code>b.length - off</code>
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        int res = super.read(b, off, len);
+        if (res > 0) {
+            count += res;
+            checkLimit();
+        }
+        return res;
+    }
+
+    /**
+     * Returns, whether this stream is already closed.
+     * @return True, if the stream is closed, otherwise false.
+     * @throws IOException An I/O error occurred.
+     */
+    public boolean isClosed() throws IOException {
+        return closed;
+    }
+
+    /**
+     * Closes this input stream and releases any system resources
+     * associated with the stream.
+     * This
+     * method simply performs <code>in.close()</code>.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    @Override
+    public void close() throws IOException {
+        closed = true;
+        super.close();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/Streams.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/Streams.java
new file mode 100644
index 0000000..00b311c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/util/Streams.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.tomcat.util.http.fileupload.InvalidFileNameException;
+
+
+/** Utility class for working with streams.
+ */
+public final class Streams {
+    /**
+     * Private constructor, to prevent instantiation.
+     * This class has only static methods.
+     */
+    private Streams() {
+        // Does nothing
+    }
+
+    /**
+     * Default buffer size for use in
+     * {@link #copy(InputStream, OutputStream, boolean)}.
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    /**
+     * Copies the contents of the given {@link InputStream}
+     * to the given {@link OutputStream}. Shortcut for
+     * <pre>
+     *   copy(pInputStream, pOutputStream, new byte[8192]);
+     * </pre>
+     * @param pInputStream The input stream, which is being read.
+     * It is guaranteed, that {@link InputStream#close()} is called
+     * on the stream.
+     * @param pOutputStream The output stream, to which data should
+     * be written. May be null, in which case the input streams
+     * contents are simply discarded.
+     * @param pClose True guarantees, that {@link OutputStream#close()}
+     * is called on the stream. False indicates, that only
+     * {@link OutputStream#flush()} should be called finally.
+     *
+     * @return Number of bytes, which have been copied.
+     * @throws IOException An I/O error occurred.
+     */
+    public static long copy(InputStream pInputStream,
+            OutputStream pOutputStream, boolean pClose)
+            throws IOException {
+        return copy(pInputStream, pOutputStream, pClose,
+                new byte[DEFAULT_BUFFER_SIZE]);
+    }
+
+    /**
+     * Copies the contents of the given {@link InputStream}
+     * to the given {@link OutputStream}.
+     * @param pIn The input stream, which is being read.
+     *   It is guaranteed, that {@link InputStream#close()} is called
+     *   on the stream.
+     * @param pOut The output stream, to which data should
+     *   be written. May be null, in which case the input streams
+     *   contents are simply discarded.
+     * @param pClose True guarantees, that {@link OutputStream#close()}
+     *   is called on the stream. False indicates, that only
+     *   {@link OutputStream#flush()} should be called finally.
+     * @param pBuffer Temporary buffer, which is to be used for
+     *   copying data.
+     * @return Number of bytes, which have been copied.
+     * @throws IOException An I/O error occurred.
+     */
+    public static long copy(InputStream pIn,
+            OutputStream pOut, boolean pClose,
+            byte[] pBuffer)
+    throws IOException {
+        OutputStream out = pOut;
+        InputStream in = pIn;
+        try {
+            long total = 0;
+            for (;;) {
+                int res = in.read(pBuffer);
+                if (res == -1) {
+                    break;
+                }
+                if (res > 0) {
+                    total += res;
+                    if (out != null) {
+                        out.write(pBuffer, 0, res);
+                    }
+                }
+            }
+            if (out != null) {
+                if (pClose) {
+                    out.close();
+                } else {
+                    out.flush();
+                }
+                out = null;
+            }
+            in.close();
+            in = null;
+            return total;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException ioe) {
+                    /* Ignore me */
+                }
+            }
+            if (pClose  &&  out != null) {
+                try {
+                    out.close();
+                } catch (IOException ioe) {
+                    /* Ignore me */
+                }
+            }
+        }
+    }
+
+    /**
+     * This convenience method allows to read a
+     * {@link org.apache.tomcat.util.http.fileupload.FileItemStream}'s
+     * content into a string. The platform's default character encoding
+     * is used for converting bytes into characters.
+     * @param pStream The input stream to read.
+     * @see #asString(InputStream, String)
+     * @return The streams contents, as a string.
+     * @throws IOException An I/O error occurred.
+     */
+    public static String asString(InputStream pStream) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        copy(pStream, baos, true);
+        return baos.toString();
+    }
+
+    /**
+     * This convenience method allows to read a
+     * {@link org.apache.tomcat.util.http.fileupload.FileItemStream}'s
+     * content into a string, using the given character encoding.
+     * @param pStream The input stream to read.
+     * @param pEncoding The character encoding, typically "UTF-8".
+     * @see #asString(InputStream)
+     * @return The streams contents, as a string.
+     * @throws IOException An I/O error occurred.
+     */
+    public static String asString(InputStream pStream, String pEncoding)
+            throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        copy(pStream, baos, true);
+        return baos.toString(pEncoding);
+    }
+
+    /**
+     * Checks, whether the given file name is valid in the sense,
+     * that it doesn't contain any NUL characters. If the file name
+     * is valid, it will be returned without any modifications. Otherwise,
+     * an {@link InvalidFileNameException} is raised.
+     * @param pFileName The file name to check
+     * @return Unmodified file name, if valid.
+     * @throws InvalidFileNameException The file name was found to be invalid.
+     */
+    public static String checkFileName(String pFileName) {
+        if (pFileName != null  &&  pFileName.indexOf('\u0000') != -1) {
+            // pFileName.replace("\u0000", "\\0")
+            final StringBuffer sb = new StringBuffer();
+            for (int i = 0;  i < pFileName.length();  i++) {
+                char c = pFileName.charAt(i);
+                switch (c) {
+                    case 0:
+                        sb.append("\\0");
+                        break;
+                    default:
+                        sb.append(c);
+                        break;
+                }
+            }
+            throw new InvalidFileNameException(pFileName,
+                    "Invalid file name: " + sb);
+        }
+        return pFileName;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/LocalStrings.properties
new file mode 100644
index 0000000..953aaeb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/LocalStrings.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+mapper.removeWrapper=Removing wrapper from Context [{0}] with path [{1}]
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/Mapper.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/Mapper.java
new file mode 100644
index 0000000..4621e26
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/Mapper.java
@@ -0,0 +1,1505 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.http.mapper;
+
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.apache.tomcat.util.buf.Ascii;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Mapper, which implements the servlet API mapping rules (which are derived
+ * from the HTTP rules).
+ *
+ * @author Remy Maucherat
+ */
+public final class Mapper {
+
+
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(Mapper.class);
+    
+    protected static final StringManager sm =
+        StringManager.getManager(Mapper.class.getPackage().getName());
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Array containing the virtual hosts definitions.
+     */
+    protected Host[] hosts = new Host[0];
+
+
+    /**
+     * Default host name.
+     */
+    protected String defaultHostName = null;
+
+    /**
+     * Context associated with this wrapper, used for wrapper mapping.
+     */
+    protected ContextVersion context = new ContextVersion();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Set default host.
+     *
+     * @param defaultHostName Default host name
+     */
+    public void setDefaultHostName(String defaultHostName) {
+        this.defaultHostName = defaultHostName;
+    }
+
+    /**
+     * Add a new host to the mapper.
+     *
+     * @param name Virtual host name
+     * @param host Host object
+     */
+    public synchronized void addHost(String name, String[] aliases,
+                                     Object host) {
+        Host[] newHosts = new Host[hosts.length + 1];
+        Host newHost = new Host();
+        ContextList contextList = new ContextList();
+        newHost.name = name;
+        newHost.contextList = contextList;
+        newHost.object = host;
+        if (insertMap(hosts, newHosts, newHost)) {
+            hosts = newHosts;
+        }
+        for (int i = 0; i < aliases.length; i++) {
+            newHosts = new Host[hosts.length + 1];
+            newHost = new Host();
+            newHost.name = aliases[i];
+            newHost.contextList = contextList;
+            newHost.object = host;
+            if (insertMap(hosts, newHosts, newHost)) {
+                hosts = newHosts;
+            }
+        }
+    }
+
+
+    /**
+     * Remove a host from the mapper.
+     *
+     * @param name Virtual host name
+     */
+    public synchronized void removeHost(String name) {
+        // Find and remove the old host
+        int pos = find(hosts, name);
+        if (pos < 0) {
+            return;
+        }
+        Object host = hosts[pos].object;
+        Host[] newHosts = new Host[hosts.length - 1];
+        if (removeMap(hosts, newHosts, name)) {
+            hosts = newHosts;
+        }
+        // Remove all aliases (they will map to the same host object)
+        for (int i = 0; i < newHosts.length; i++) {
+            if (newHosts[i].object == host) {
+                Host[] newHosts2 = new Host[hosts.length - 1];
+                if (removeMap(hosts, newHosts2, newHosts[i].name)) {
+                    hosts = newHosts2;
+                }
+            }
+        }
+    }
+
+    /**
+     * Add an alias to an existing host.
+     * @param name  The name of the host
+     * @param alias The alias to add
+     */
+    public synchronized void addHostAlias(String name, String alias) {
+        int pos = find(hosts, name);
+        if (pos < 0) {
+            // Should not be adding an alias for a host that doesn't exist but
+            // just in case...
+            return;
+        }
+        Host realHost = hosts[pos];
+        
+        Host[] newHosts = new Host[hosts.length + 1];
+        Host newHost = new Host();
+        newHost.name = alias;
+        newHost.contextList = realHost.contextList;
+        newHost.object = realHost;
+        if (insertMap(hosts, newHosts, newHost)) {
+            hosts = newHosts;
+        }
+    }
+
+    /**
+     * Remove a host alias
+     * @param alias The alias to remove
+     */
+    public synchronized void removeHostAlias(String alias) {
+        // Find and remove the alias
+        int pos = find(hosts, alias);
+        if (pos < 0) {
+            return;
+        }
+        Host[] newHosts = new Host[hosts.length - 1];
+        if (removeMap(hosts, newHosts, alias)) {
+            hosts = newHosts;
+        }
+
+    }
+
+
+    /**
+     * Set context, used for wrapper mapping (request dispatcher).
+     *
+     * @param welcomeResources Welcome files defined for this context
+     * @param resources Static resources of the context
+     */
+    public void setContext(String path, String[] welcomeResources,
+                           javax.naming.Context resources) {
+        context.path = path;
+        context.welcomeResources = welcomeResources;
+        context.resources = resources;
+    }
+
+
+    /**
+     * Add a new Context to an existing Host.
+     *
+     * @param hostName Virtual host name this context belongs to
+     * @param host Host object
+     * @param path Context path
+     * @param version Context version
+     * @param context Context object
+     * @param welcomeResources Welcome files defined for this context
+     * @param resources Static resources of the context
+     */
+    public void addContextVersion(String hostName, Object host, String path,
+            String version, Object context, String[] welcomeResources,
+            javax.naming.Context resources) {
+
+        Host[] hosts = this.hosts;
+        int pos = find(hosts, hostName);
+        if( pos <0 ) {
+            addHost(hostName, new String[0], host);
+            hosts = this.hosts;
+            pos = find(hosts, hostName);
+        }
+        if (pos < 0) {
+            log.error("No host found: " + hostName);
+        }
+        Host mappedHost = hosts[pos];
+        if (mappedHost.name.equals(hostName)) {
+            int slashCount = slashCount(path);
+            synchronized (mappedHost) {
+                Context[] contexts = mappedHost.contextList.contexts;
+                // Update nesting
+                if (slashCount > mappedHost.contextList.nesting) {
+                    mappedHost.contextList.nesting = slashCount;
+                }
+                int pos2 = find(contexts, path);
+                if (pos2 < 0 || !path.equals(contexts[pos2].name)) {
+                    Context newContext = new Context();
+                    newContext.name = path;
+                    Context[] newContexts = new Context[contexts.length + 1];
+                    if (insertMap(contexts, newContexts, newContext)) {
+                        mappedHost.contextList.contexts = newContexts;
+                    }
+                    pos2 = find(newContexts, path);
+                }
+                
+                Context mappedContext = mappedHost.contextList.contexts[pos2];
+                
+                ContextVersion[] contextVersions = mappedContext.versions;
+                ContextVersion[] newContextVersions =
+                    new ContextVersion[contextVersions.length + 1];
+                ContextVersion newContextVersion = new ContextVersion();
+                newContextVersion.path = path;
+                newContextVersion.name = version;
+                newContextVersion.object = context;
+                newContextVersion.welcomeResources = welcomeResources;
+                newContextVersion.resources = resources;
+                if (insertMap(contextVersions, newContextVersions, newContextVersion)) {
+                    mappedContext.versions = newContextVersions;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Remove a context from an existing host.
+     *
+     * @param hostName Virtual host name this context belongs to
+     * @param path Context path
+     * @param version Context version
+     */
+    public void removeContextVersion(String hostName, String path,
+            String version) {
+        Host[] hosts = this.hosts;
+        int pos = find(hosts, hostName);
+        if (pos < 0) {
+            return;
+        }
+        Host host = hosts[pos];
+        if (host.name.equals(hostName)) {
+            synchronized (host) {
+                Context[] contexts = host.contextList.contexts;
+                if (contexts.length == 0 ){
+                    return;
+                }
+                
+                int pos2 = find(contexts, path);
+                if (pos2 < 0 || !path.equals(contexts[pos2].name)) {
+                    return;
+                }
+                Context context = contexts[pos2];
+                
+                ContextVersion[] contextVersions = context.versions;
+                ContextVersion[] newContextVersions =
+                    new ContextVersion[contextVersions.length - 1];
+                if (removeMap(contextVersions, newContextVersions, version)) {
+                    context.versions = newContextVersions;
+                    
+                    if (context.versions.length == 0) {
+                        // Remove the context
+                        Context[] newContexts = new Context[contexts.length -1];
+                        if (removeMap(contexts, newContexts, path)) {
+                            host.contextList.contexts = newContexts;
+                            // Recalculate nesting
+                            host.contextList.nesting = 0;
+                            for (int i = 0; i < newContexts.length; i++) {
+                                int slashCount = slashCount(newContexts[i].name);
+                                if (slashCount > host.contextList.nesting) {
+                                    host.contextList.nesting = slashCount;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    public void addWrapper(String hostName, String contextPath, String version,
+                           String path, Object wrapper, boolean jspWildCard,
+                           boolean resourceOnly) {
+        Host[] hosts = this.hosts;
+        int pos = find(hosts, hostName);
+        if (pos < 0) {
+            return;
+        }
+        Host host = hosts[pos];
+        if (host.name.equals(hostName)) {
+            Context[] contexts = host.contextList.contexts;
+            int pos2 = find(contexts, contextPath);
+            if (pos2 < 0) {
+                log.error("No context found: " + contextPath );
+                return;
+            }
+            Context context = contexts[pos2];
+            if (context.name.equals(contextPath)) {
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " +
+                            version);
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    addWrapper(contextVersion, path, wrapper, jspWildCard,
+                            resourceOnly);
+                }
+            }
+        }
+    }
+
+
+    public void addWrapper(String path, Object wrapper, boolean jspWildCard,
+            boolean resourceOnly) {
+        addWrapper(context, path, wrapper, jspWildCard, resourceOnly);
+    }
+
+
+    /**
+     * Adds a wrapper to the given context.
+     *
+     * @param context The context to which to add the wrapper
+     * @param path Wrapper mapping
+     * @param wrapper The Wrapper object
+     * @param jspWildCard true if the wrapper corresponds to the JspServlet
+     * @param resourceOnly true if this wrapper always expects a physical
+     *                     resource to be present (such as a JSP)
+     * and the mapping path contains a wildcard; false otherwise
+     */
+    protected void addWrapper(ContextVersion context, String path,
+            Object wrapper, boolean jspWildCard, boolean resourceOnly) {
+
+        synchronized (context) {
+            Wrapper newWrapper = new Wrapper();
+            newWrapper.object = wrapper;
+            newWrapper.jspWildCard = jspWildCard;
+            newWrapper.resourceOnly = resourceOnly;
+            if (path.endsWith("/*")) {
+                // Wildcard wrapper
+                newWrapper.name = path.substring(0, path.length() - 2);
+                Wrapper[] oldWrappers = context.wildcardWrappers;
+                Wrapper[] newWrappers =
+                    new Wrapper[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.wildcardWrappers = newWrappers;
+                    int slashCount = slashCount(newWrapper.name);
+                    if (slashCount > context.nesting) {
+                        context.nesting = slashCount;
+                    }
+                }
+            } else if (path.startsWith("*.")) {
+                // Extension wrapper
+                newWrapper.name = path.substring(2);
+                Wrapper[] oldWrappers = context.extensionWrappers;
+                Wrapper[] newWrappers =
+                    new Wrapper[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.extensionWrappers = newWrappers;
+                }
+            } else if (path.equals("/")) {
+                // Default wrapper
+                newWrapper.name = "";
+                context.defaultWrapper = newWrapper;
+            } else {
+                // Exact wrapper
+                newWrapper.name = path;
+                Wrapper[] oldWrappers = context.exactWrappers;
+                Wrapper[] newWrappers =
+                    new Wrapper[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.exactWrappers = newWrappers;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Remove a wrapper from the context associated with this wrapper.
+     *
+     * @param path Wrapper mapping
+     */
+    public void removeWrapper(String path) {
+        removeWrapper(context, path);
+    }
+
+
+    /**
+     * Remove a wrapper from an existing context.
+     *
+     * @param hostName Virtual host name this wrapper belongs to
+     * @param contextPath Context path this wrapper belongs to
+     * @param path Wrapper mapping
+     */
+    public void removeWrapper
+        (String hostName, String contextPath, String version, String path) {
+        Host[] hosts = this.hosts;
+        int pos = find(hosts, hostName);
+        if (pos < 0) {
+            return;
+        }
+        Host host = hosts[pos];
+        if (host.name.equals(hostName)) {
+            Context[] contexts = host.contextList.contexts;
+            int pos2 = find(contexts, contextPath);
+            if (pos2 < 0) {
+                return;
+            }
+            Context context = contexts[pos2];
+            if (context.name.equals(contextPath)) {
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    removeWrapper(contextVersion, path);
+                }
+            }
+        }
+    }
+
+    protected void removeWrapper(ContextVersion context, String path) {
+        
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("mapper.removeWrapper", context.name, path));
+        }
+
+        synchronized (context) {
+            if (path.endsWith("/*")) {
+                // Wildcard wrapper
+                String name = path.substring(0, path.length() - 2);
+                Wrapper[] oldWrappers = context.wildcardWrappers;
+                if (oldWrappers.length == 0) {
+                    return;
+                }
+                Wrapper[] newWrappers =
+                    new Wrapper[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    // Recalculate nesting
+                    context.nesting = 0;
+                    for (int i = 0; i < newWrappers.length; i++) {
+                        int slashCount = slashCount(newWrappers[i].name);
+                        if (slashCount > context.nesting) {
+                            context.nesting = slashCount;
+                        }
+                    }
+                    context.wildcardWrappers = newWrappers;
+                }
+            } else if (path.startsWith("*.")) {
+                // Extension wrapper
+                String name = path.substring(2);
+                Wrapper[] oldWrappers = context.extensionWrappers;
+                if (oldWrappers.length == 0) {
+                    return;
+                }
+                Wrapper[] newWrappers =
+                    new Wrapper[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    context.extensionWrappers = newWrappers;
+                }
+            } else if (path.equals("/")) {
+                // Default wrapper
+                context.defaultWrapper = null;
+            } else {
+                // Exact wrapper
+                String name = path;
+                Wrapper[] oldWrappers = context.exactWrappers;
+                if (oldWrappers.length == 0) {
+                    return;
+                }
+                Wrapper[] newWrappers =
+                    new Wrapper[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    context.exactWrappers = newWrappers;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Add a welcome file to the given context.
+     * 
+     * @param hostName
+     * @param contextPath
+     * @param welcomeFile
+     */
+    public void addWelcomeFile(String hostName, String contextPath,
+            String version, String welcomeFile) {
+        Host[] hosts = this.hosts;
+        int pos = find(hosts, hostName);
+        if (pos < 0) {
+            return;
+        }
+        Host host = hosts[pos];
+        if (host.name.equals(hostName)) {
+            Context[] contexts = host.contextList.contexts;
+            int pos2 = find(contexts, contextPath);
+            if (pos2 < 0) {
+                log.error("No context found: " + contextPath );
+                return;
+            }
+            Context context = contexts[pos2];
+            if (context.name.equals(contextPath)) {
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " +
+                            version);
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    int len = contextVersion.welcomeResources.length + 1;
+                    String[] newWelcomeResources = new String[len];
+                    System.arraycopy(contextVersion.welcomeResources, 0,
+                            newWelcomeResources, 0, len - 1);
+                    newWelcomeResources[len - 1] = welcomeFile;
+                    contextVersion.welcomeResources = newWelcomeResources;
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Remove a welcome file from the given context.
+     * 
+     * @param hostName
+     * @param contextPath
+     * @param welcomeFile
+     */
+    public void removeWelcomeFile(String hostName, String contextPath,
+            String version, String welcomeFile) {
+        Host[] hosts = this.hosts;
+        int pos = find(hosts, hostName);
+        if (pos < 0) {
+            return;
+        }
+        Host host = hosts[pos];
+        if (host.name.equals(hostName)) {
+            Context[] contexts = host.contextList.contexts;
+            int pos2 = find(contexts, contextPath);
+            if (pos2 < 0) {
+                log.error("No context found: " + contextPath );
+                return;
+            }
+            Context context = contexts[pos2];
+            if (context.name.equals(contextPath)) {
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " +
+                            version);
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    int match = -1;
+                    for (int i = 0; i < contextVersion.welcomeResources.length; i++) {
+                        if (welcomeFile.equals(contextVersion.welcomeResources[i])) {
+                            match = i;
+                            break;
+                        }
+                    }
+                    if (match > -1) {
+                        int len = contextVersion.welcomeResources.length - 1;
+                        String[] newWelcomeResources = new String[len];
+                        System.arraycopy(contextVersion.welcomeResources, 0,
+                                newWelcomeResources, 0, match);
+                        if (match < len) {
+                            System.arraycopy(contextVersion.welcomeResources, match + 1,
+                                    newWelcomeResources, match, len - match);
+                        }
+                        contextVersion.welcomeResources = newWelcomeResources;
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Clear the welcome files for the given context.
+     * 
+     * @param hostName
+     * @param contextPath
+     */
+    public void clearWelcomeFiles(String hostName, String contextPath,
+            String version) {
+        Host[] hosts = this.hosts;
+        int pos = find(hosts, hostName);
+        if (pos < 0) {
+            return;
+        }
+        Host host = hosts[pos];
+        if (host.name.equals(hostName)) {
+            Context[] contexts = host.contextList.contexts;
+            int pos2 = find(contexts, contextPath);
+            if (pos2 < 0) {
+                log.error("No context found: " + contextPath );
+                return;
+            }
+            Context context = contexts[pos2];
+            if (context.name.equals(contextPath)) {
+                ContextVersion[] contextVersions = context.versions;
+                int pos3 = find(contextVersions, version);
+                if( pos3<0 ) {
+                    log.error("No context version found: " + contextPath + " " +
+                            version);
+                    return;
+                }
+                ContextVersion contextVersion = contextVersions[pos3];
+                if (contextVersion.name.equals(version)) {
+                    contextVersion.welcomeResources = new String[0];
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Map the specified host name and URI, mutating the given mapping data.
+     *
+     * @param host Virtual host name
+     * @param uri URI
+     * @param mappingData This structure will contain the result of the mapping
+     *                    operation
+     */
+    public void map(MessageBytes host, MessageBytes uri, String version,
+                    MappingData mappingData)
+        throws Exception {
+
+        if (host.isNull()) {
+            host.getCharChunk().append(defaultHostName);
+        }
+        host.toChars();
+        uri.toChars();
+        internalMap(host.getCharChunk(), uri.getCharChunk(), version,
+                mappingData);
+
+    }
+
+
+    /**
+     * Map the specified URI relative to the context,
+     * mutating the given mapping data.
+     *
+     * @param uri URI
+     * @param mappingData This structure will contain the result of the mapping
+     *                    operation
+     */
+    public void map(MessageBytes uri, MappingData mappingData)
+        throws Exception {
+
+        uri.toChars();
+        CharChunk uricc = uri.getCharChunk();
+        uricc.setLimit(-1);
+        internalMapWrapper(context, uricc, mappingData);
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Map the specified URI.
+     */
+    private final void internalMap(CharChunk host, CharChunk uri,
+            String version, MappingData mappingData) throws Exception {
+
+        uri.setLimit(-1);
+
+        Context[] contexts = null;
+        Context context = null;
+        ContextVersion contextVersion = null;
+        
+        int nesting = 0;
+
+        // Virtual host mapping
+        if (mappingData.host == null) {
+            Host[] hosts = this.hosts;
+            int pos = findIgnoreCase(hosts, host);
+            if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) {
+                mappingData.host = hosts[pos].object;
+                contexts = hosts[pos].contextList.contexts;
+                nesting = hosts[pos].contextList.nesting;
+            } else {
+                if (defaultHostName == null) {
+                    return;
+                }
+                pos = find(hosts, defaultHostName);
+                if ((pos != -1) && (defaultHostName.equals(hosts[pos].name))) {
+                    mappingData.host = hosts[pos].object;
+                    contexts = hosts[pos].contextList.contexts;
+                    nesting = hosts[pos].contextList.nesting;
+                } else {
+                    return;
+                }
+            }
+        }
+
+        // Context mapping
+        if (mappingData.context == null) {
+            int pos = find(contexts, uri);
+            if (pos == -1) {
+                return;
+            }
+
+            int lastSlash = -1;
+            int uriEnd = uri.getEnd();
+            int length = -1;
+            boolean found = false;
+            while (pos >= 0) {
+                if (uri.startsWith(contexts[pos].name)) {
+                    length = contexts[pos].name.length();
+                    if (uri.getLength() == length) {
+                        found = true;
+                        break;
+                    } else if (uri.startsWithIgnoreCase("/", length)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (lastSlash == -1) {
+                    lastSlash = nthSlash(uri, nesting + 1);
+                } else {
+                    lastSlash = lastSlash(uri);
+                }
+                uri.setEnd(lastSlash);
+                pos = find(contexts, uri);
+            }
+            uri.setEnd(uriEnd);
+
+            if (!found) {
+                if (contexts[0].name.equals("")) {
+                    context = contexts[0];
+                }
+            } else {
+                context = contexts[pos];
+            }
+            if (context != null) {
+                mappingData.contextPath.setString(context.name);
+            }
+        }
+
+        if (context != null) {
+            ContextVersion[] contextVersions = context.versions;
+            int versionCount = contextVersions.length;
+            if (versionCount > 1) {
+                Object[] contextObjects = new Object[contextVersions.length];
+                for (int i = 0; i < contextObjects.length; i++) {
+                    contextObjects[i] = contextVersions[i].object;
+                }
+                mappingData.contexts = contextObjects;
+            }
+            
+            if (version == null) {
+                // Return the latest version
+                contextVersion = contextVersions[versionCount - 1];
+            } else {
+                int pos = find(contextVersions, version);
+                if (pos < 0 || !contextVersions[pos].name.equals(version)) {
+                    // Return the latest version
+                    contextVersion = contextVersions[versionCount - 1];
+                } else {
+                    contextVersion = contextVersions[pos];
+                }
+            }
+            mappingData.context = contextVersion.object;
+        }
+
+        // Wrapper mapping
+        if ((contextVersion != null) && (mappingData.wrapper == null)) {
+            internalMapWrapper(contextVersion, uri, mappingData);
+        }
+
+    }
+
+
+    /**
+     * Wrapper mapping.
+     */
+    private final void internalMapWrapper(ContextVersion contextVersion,
+                                          CharChunk path,
+                                          MappingData mappingData)
+        throws Exception {
+
+        int pathOffset = path.getOffset();
+        int pathEnd = path.getEnd();
+        int servletPath = pathOffset;
+        boolean noServletPath = false;
+
+        int length = contextVersion.path.length();
+        if (length != (pathEnd - pathOffset)) {
+            servletPath = pathOffset + length;
+        } else {
+            noServletPath = true;
+            path.append('/');
+            pathOffset = path.getOffset();
+            pathEnd = path.getEnd();
+            servletPath = pathOffset+length;
+        }
+
+        path.setOffset(servletPath);
+
+        // Rule 1 -- Exact Match
+        Wrapper[] exactWrappers = contextVersion.exactWrappers;
+        internalMapExactWrapper(exactWrappers, path, mappingData);
+
+        // Rule 2 -- Prefix Match
+        boolean checkJspWelcomeFiles = false;
+        Wrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
+        if (mappingData.wrapper == null) {
+            internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting, 
+                                       path, mappingData);
+            if (mappingData.wrapper != null && mappingData.jspWildCard) {
+                char[] buf = path.getBuffer();
+                if (buf[pathEnd - 1] == '/') {
+                    /*
+                     * Path ending in '/' was mapped to JSP servlet based on
+                     * wildcard match (e.g., as specified in url-pattern of a
+                     * jsp-property-group.
+                     * Force the context's welcome files, which are interpreted
+                     * as JSP files (since they match the url-pattern), to be
+                     * considered. See Bugzilla 27664.
+                     */ 
+                    mappingData.wrapper = null;
+                    checkJspWelcomeFiles = true;
+                } else {
+                    // See Bugzilla 27704
+                    mappingData.wrapperPath.setChars(buf, path.getStart(),
+                                                     path.getLength());
+                    mappingData.pathInfo.recycle();
+                }
+            }
+        }
+
+        if(mappingData.wrapper == null && noServletPath) {
+            // The path is empty, redirect to "/"
+            mappingData.redirectPath.setChars
+                (path.getBuffer(), pathOffset, pathEnd-pathOffset);
+            path.setEnd(pathEnd - 1);
+            return;
+        }
+
+        // Rule 3 -- Extension Match
+        Wrapper[] extensionWrappers = contextVersion.extensionWrappers;
+        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
+            internalMapExtensionWrapper(extensionWrappers, path, mappingData,
+                    true);
+        }
+
+        // Rule 4 -- Welcome resources processing for servlets
+        if (mappingData.wrapper == null) {
+            boolean checkWelcomeFiles = checkJspWelcomeFiles;
+            if (!checkWelcomeFiles) {
+                char[] buf = path.getBuffer();
+                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
+            }
+            if (checkWelcomeFiles) {
+                for (int i = 0; (i < contextVersion.welcomeResources.length)
+                         && (mappingData.wrapper == null); i++) {
+                    path.setOffset(pathOffset);
+                    path.setEnd(pathEnd);
+                    path.append(contextVersion.welcomeResources[i], 0,
+                            contextVersion.welcomeResources[i].length());
+                    path.setOffset(servletPath);
+
+                    // Rule 4a -- Welcome resources processing for exact macth
+                    internalMapExactWrapper(exactWrappers, path, mappingData);
+
+                    // Rule 4b -- Welcome resources processing for prefix match
+                    if (mappingData.wrapper == null) {
+                        internalMapWildcardWrapper
+                            (wildcardWrappers, contextVersion.nesting, 
+                             path, mappingData);
+                    }
+
+                    // Rule 4c -- Welcome resources processing
+                    //            for physical folder
+                    if (mappingData.wrapper == null
+                        && contextVersion.resources != null) {
+                        Object file = null;
+                        String pathStr = path.toString();
+                        try {
+                            file = contextVersion.resources.lookup(pathStr);
+                        } catch(NamingException nex) {
+                            // Swallow not found, since this is normal
+                        }
+                        if (file != null && !(file instanceof DirContext) ) {
+                            internalMapExtensionWrapper(extensionWrappers, path,
+                                                        mappingData, true);
+                            if (mappingData.wrapper == null
+                                && contextVersion.defaultWrapper != null) {
+                                mappingData.wrapper =
+                                    contextVersion.defaultWrapper.object;
+                                mappingData.requestPath.setChars
+                                    (path.getBuffer(), path.getStart(), 
+                                     path.getLength());
+                                mappingData.wrapperPath.setChars
+                                    (path.getBuffer(), path.getStart(), 
+                                     path.getLength());
+                                mappingData.requestPath.setString(pathStr);
+                                mappingData.wrapperPath.setString(pathStr);
+                            }
+                        }
+                    }
+                }
+
+                path.setOffset(servletPath);
+                path.setEnd(pathEnd);
+            }
+                                        
+        }
+
+        /* welcome file processing - take 2
+         * Now that we have looked for welcome files with a physical
+         * backing, now look for an extension mapping listed
+         * but may not have a physical backing to it. This is for 
+         * the case of index.jsf, index.do, etc.
+         * A watered down version of rule 4
+         */
+        if (mappingData.wrapper == null) {
+            boolean checkWelcomeFiles = checkJspWelcomeFiles;
+            if (!checkWelcomeFiles) {
+                char[] buf = path.getBuffer();
+                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
+            }
+            if (checkWelcomeFiles) {
+                for (int i = 0; (i < contextVersion.welcomeResources.length)
+                         && (mappingData.wrapper == null); i++) {
+                    path.setOffset(pathOffset);
+                    path.setEnd(pathEnd);
+                    path.append(contextVersion.welcomeResources[i], 0,
+                                contextVersion.welcomeResources[i].length());
+                    path.setOffset(servletPath);
+                    internalMapExtensionWrapper(extensionWrappers, path,
+                                                mappingData, false);
+                }
+
+                path.setOffset(servletPath);
+                path.setEnd(pathEnd);
+            }
+        }
+
+
+        // Rule 7 -- Default servlet
+        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
+            if (contextVersion.defaultWrapper != null) {
+                mappingData.wrapper = contextVersion.defaultWrapper.object;
+                mappingData.requestPath.setChars
+                    (path.getBuffer(), path.getStart(), path.getLength());
+                mappingData.wrapperPath.setChars
+                    (path.getBuffer(), path.getStart(), path.getLength());
+            }
+            // Redirection to a folder
+            char[] buf = path.getBuffer();
+            if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
+                Object file = null;
+                String pathStr = path.toString();
+                try {
+                    file = contextVersion.resources.lookup(pathStr);
+                } catch(NamingException nex) {
+                    // Swallow, since someone else handles the 404
+                }
+                if (file != null && file instanceof DirContext) {
+                    // Note: this mutates the path: do not do any processing 
+                    // after this (since we set the redirectPath, there 
+                    // shouldn't be any)
+                    path.setOffset(pathOffset);
+                    path.append('/');
+                    mappingData.redirectPath.setChars
+                        (path.getBuffer(), path.getStart(), path.getLength());
+                } else {
+                    mappingData.requestPath.setString(pathStr);
+                    mappingData.wrapperPath.setString(pathStr);
+                }
+            }
+        }
+
+        path.setOffset(pathOffset);
+        path.setEnd(pathEnd);
+
+    }
+
+
+    /**
+     * Exact mapping.
+     */
+    private final void internalMapExactWrapper
+        (Wrapper[] wrappers, CharChunk path, MappingData mappingData) {
+        int pos = find(wrappers, path);
+        if ((pos != -1) && (path.equals(wrappers[pos].name))) {
+            mappingData.requestPath.setString(wrappers[pos].name);
+            mappingData.wrapperPath.setString(wrappers[pos].name);
+            mappingData.wrapper = wrappers[pos].object;
+        }
+    }
+
+
+    /**
+     * Wildcard mapping.
+     */
+    private final void internalMapWildcardWrapper
+        (Wrapper[] wrappers, int nesting, CharChunk path, 
+         MappingData mappingData) {
+
+        int pathEnd = path.getEnd();
+
+        int lastSlash = -1;
+        int length = -1;
+        int pos = find(wrappers, path);
+        if (pos != -1) {
+            boolean found = false;
+            while (pos >= 0) {
+                if (path.startsWith(wrappers[pos].name)) {
+                    length = wrappers[pos].name.length();
+                    if (path.getLength() == length) {
+                        found = true;
+                        break;
+                    } else if (path.startsWithIgnoreCase("/", length)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (lastSlash == -1) {
+                    lastSlash = nthSlash(path, nesting + 1);
+                } else {
+                    lastSlash = lastSlash(path);
+                }
+                path.setEnd(lastSlash);
+                pos = find(wrappers, path);
+            }
+            path.setEnd(pathEnd);
+            if (found) {
+                mappingData.wrapperPath.setString(wrappers[pos].name);
+                if (path.getLength() > length) {
+                    mappingData.pathInfo.setChars
+                        (path.getBuffer(),
+                         path.getOffset() + length,
+                         path.getLength() - length);
+                }
+                mappingData.requestPath.setChars
+                    (path.getBuffer(), path.getOffset(), path.getLength());
+                mappingData.wrapper = wrappers[pos].object;
+                mappingData.jspWildCard = wrappers[pos].jspWildCard;
+            }
+        }
+    }
+
+
+    /**
+     * Extension mappings.
+     * 
+     * @param wrappers          Set of wrappers to check for matches
+     * @param path              Path to map
+     * @param mappingData       Mapping data for result
+     * @param resourceExpected  Is this mapping expecting to find a resource
+     */
+    private final void internalMapExtensionWrapper(Wrapper[] wrappers,
+            CharChunk path, MappingData mappingData, boolean resourceExpected) {
+        char[] buf = path.getBuffer();
+        int pathEnd = path.getEnd();
+        int servletPath = path.getOffset();
+        int slash = -1;
+        for (int i = pathEnd - 1; i >= servletPath; i--) {
+            if (buf[i] == '/') {
+                slash = i;
+                break;
+            }
+        }
+        if (slash >= 0) {
+            int period = -1;
+            for (int i = pathEnd - 1; i > slash; i--) {
+                if (buf[i] == '.') {
+                    period = i;
+                    break;
+                }
+            }
+            if (period >= 0) {
+                path.setOffset(period + 1);
+                path.setEnd(pathEnd);
+                int pos = find(wrappers, path);
+                if ((pos != -1) && (path.equals(wrappers[pos].name)) &&
+                        (resourceExpected || !wrappers[pos].resourceOnly)) {
+                    mappingData.wrapperPath.setChars
+                        (buf, servletPath, pathEnd - servletPath);
+                    mappingData.requestPath.setChars
+                        (buf, servletPath, pathEnd - servletPath);
+                    mappingData.wrapper = wrappers[pos].object;
+                }
+                path.setOffset(servletPath);
+                path.setEnd(pathEnd);
+            }
+        }
+    }
+
+
+    /**
+     * Find a map element given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int find(MapElement[] map, CharChunk name) {
+        return find(map, name, name.getStart(), name.getEnd());
+    }
+
+
+    /**
+     * Find a map element given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int find(MapElement[] map, CharChunk name,
+                                  int start, int end) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (compare(name, start, end, map[0].name) < 0 ) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = compare(name, start, end, map[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compare(name, start, end, map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Find a map element given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int findIgnoreCase(MapElement[] map, CharChunk name) {
+        return findIgnoreCase(map, name, name.getStart(), name.getEnd());
+    }
+
+
+    /**
+     * Find a map element given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int findIgnoreCase(MapElement[] map, CharChunk name,
+                                  int start, int end) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        if (compareIgnoreCase(name, start, end, map[0].name) < 0 ) {
+            return -1;
+        }         
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = compareIgnoreCase(name, start, end, map[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = compareIgnoreCase(name, start, end, map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Find a map element given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int find(MapElement[] map, String name) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        
+        if (name.compareTo(map[0].name) < 0) {
+            return -1;
+        } 
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = name.compareTo(map[i].name);
+            if (result > 0) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = name.compareTo(map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Compare given char chunk with String.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    private static final int compare(CharChunk name, int start, int end,
+                                     String compareTo) {
+        int result = 0;
+        char[] c = name.getBuffer();
+        int len = compareTo.length();
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (c[i + start] > compareTo.charAt(i)) {
+                result = 1;
+            } else if (c[i + start] < compareTo.charAt(i)) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length() > (end - start)) {
+                result = -1;
+            } else if (compareTo.length() < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Compare given char chunk with String ignoring case.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    private static final int compareIgnoreCase(CharChunk name, int start, int end,
+                                     String compareTo) {
+        int result = 0;
+        char[] c = name.getBuffer();
+        int len = compareTo.length();
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (Ascii.toLower(c[i + start]) > Ascii.toLower(compareTo.charAt(i))) {
+                result = 1;
+            } else if (Ascii.toLower(c[i + start]) < Ascii.toLower(compareTo.charAt(i))) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length() > (end - start)) {
+                result = -1;
+            } else if (compareTo.length() < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Find the position of the last slash in the given char chunk.
+     */
+    private static final int lastSlash(CharChunk name) {
+
+        char[] c = name.getBuffer();
+        int end = name.getEnd();
+        int start = name.getStart();
+        int pos = end;
+
+        while (pos > start) {
+            if (c[--pos] == '/') {
+                break;
+            }
+        }
+
+        return (pos);
+
+    }
+
+
+    /**
+     * Find the position of the nth slash, in the given char chunk.
+     */
+    private static final int nthSlash(CharChunk name, int n) {
+
+        char[] c = name.getBuffer();
+        int end = name.getEnd();
+        int start = name.getStart();
+        int pos = start;
+        int count = 0;
+
+        while (pos < end) {
+            if ((c[pos++] == '/') && ((++count) == n)) {
+                pos--;
+                break;
+            }
+        }
+
+        return (pos);
+
+    }
+
+
+    /**
+     * Return the slash count in a given string.
+     */
+    private static final int slashCount(String name) {
+        int pos = -1;
+        int count = 0;
+        while ((pos = name.indexOf('/', pos + 1)) != -1) {
+            count++;
+        }
+        return count;
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array, and prevent
+     * duplicates.
+     */
+    private static final boolean insertMap
+        (MapElement[] oldMap, MapElement[] newMap, MapElement newElement) {
+        int pos = find(oldMap, newElement.name);
+        if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) {
+            return false;
+        }
+        System.arraycopy(oldMap, 0, newMap, 0, pos + 1);
+        newMap[pos + 1] = newElement;
+        System.arraycopy
+            (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1);
+        return true;
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array.
+     */
+    private static final boolean removeMap
+        (MapElement[] oldMap, MapElement[] newMap, String name) {
+        int pos = find(oldMap, name);
+        if ((pos != -1) && (name.equals(oldMap[pos].name))) {
+            System.arraycopy(oldMap, 0, newMap, 0, pos);
+            System.arraycopy(oldMap, pos + 1, newMap, pos,
+                             oldMap.length - pos - 1);
+            return true;
+        }
+        return false;
+    }
+
+
+    // ------------------------------------------------- MapElement Inner Class
+
+
+    protected abstract static class MapElement {
+
+        public String name = null;
+        public Object object = null;
+
+    }
+
+
+    // ------------------------------------------------------- Host Inner Class
+
+
+    protected static final class Host
+        extends MapElement {
+
+        public ContextList contextList = null;
+
+    }
+
+
+    // ------------------------------------------------ ContextList Inner Class
+
+
+    protected static final class ContextList {
+
+        public Context[] contexts = new Context[0];
+        public int nesting = 0;
+
+    }
+
+
+    // ---------------------------------------------------- Context Inner Class
+
+
+    protected static final class Context extends MapElement {
+        public ContextVersion[] versions = new ContextVersion[0];
+    }
+
+
+    protected static final class ContextVersion extends MapElement {
+        public String path = null;
+        public String[] welcomeResources = new String[0];
+        public javax.naming.Context resources = null;
+        public Wrapper defaultWrapper = null;
+        public Wrapper[] exactWrappers = new Wrapper[0];
+        public Wrapper[] wildcardWrappers = new Wrapper[0];
+        public Wrapper[] extensionWrappers = new Wrapper[0];
+        public int nesting = 0;
+
+    }
+
+
+    // ---------------------------------------------------- Wrapper Inner Class
+
+
+    protected static class Wrapper
+        extends MapElement {
+
+        public boolean jspWildCard = false;
+        public boolean resourceOnly = false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/MappingData.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/MappingData.java
new file mode 100644
index 0000000..5d8ab74
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/mapper/MappingData.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.http.mapper;
+
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/**
+ * Mapping data.
+ *
+ * @author Remy Maucherat
+ */
+public class MappingData {
+
+    public Object host = null;
+    public Object context = null;
+    public Object[] contexts = null;
+    public Object wrapper = null;
+    public boolean jspWildCard = false;
+
+    public MessageBytes contextPath = MessageBytes.newInstance();
+    public MessageBytes requestPath = MessageBytes.newInstance();
+    public MessageBytes wrapperPath = MessageBytes.newInstance();
+    public MessageBytes pathInfo = MessageBytes.newInstance();
+
+    public MessageBytes redirectPath = MessageBytes.newInstance();
+
+    public void recycle() {
+        host = null;
+        context = null;
+        contexts = null;
+        wrapper = null;
+        jspWildCard = false;
+        contextPath.recycle();
+        requestPath.recycle();
+        wrapperPath.recycle();
+        pathInfo.recycle();
+        redirectPath.recycle();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings.properties
new file mode 100644
index 0000000..33102df
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings.properties
@@ -0,0 +1,62 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# HttpMessages. The values in this file will be used in HTTP headers and as such
+# may only contain TEXT as defined by RFC 2616
+sc.100=Continue
+sc.101=Switching Protocols
+sc.200=OK
+sc.201=Created
+sc.202=Accepted
+sc.203=Non-Authoritative Information
+sc.204=No Content
+sc.205=Reset Content
+sc.206=Partial Content
+sc.207=Multi-Status
+sc.300=Multiple Choices
+sc.301=Moved Permanently
+sc.302=Moved Temporarily
+sc.303=See Other
+sc.304=Not Modified
+sc.305=Use Proxy
+sc.307=Temporary Redirect
+sc.400=Bad Request
+sc.401=Unauthorized
+sc.402=Payment Required
+sc.403=Forbidden
+sc.404=Not Found
+sc.405=Method Not Allowed
+sc.406=Not Acceptable
+sc.407=Proxy Authentication Required
+sc.408=Request Timeout
+sc.409=Conflict
+sc.410=Gone
+sc.411=Length Required
+sc.412=Precondition Failed
+sc.413=Request Entity Too Large
+sc.414=Request-URI Too Long
+sc.415=Unsupported Media Type
+sc.416=Requested Range Not Satisfiable
+sc.417=Expectation Failed
+sc.422=Unprocessable Entity
+sc.423=Locked
+sc.424=Failed Dependency
+sc.500=Internal Server Error
+sc.501=Not Implemented
+sc.502=Bad Gateway
+sc.503=Service Unavailable
+sc.504=Gateway Timeout
+sc.505=HTTP Version Not Supported
+sc.507=Insufficient Storage
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_es.properties
new file mode 100644
index 0000000..d3f3837
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_es.properties
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# HttpMessages. The values in this file will be used in HTTP headers and as such
+# may only contain TEXT as defined by RFC 2616
+sc.100=Continuar
+sc.101=Cambiando Protocolos
+sc.200=OK
+sc.201=Creado
+sc.202=Aceptado
+sc.203=Informaci\u00f3n No-Autorizativa
+sc.204=Sin Contenido
+sc.205=Reset Contenido
+sc.206=Contenido Parcial
+sc.207=Multi-Estado
+sc.300=M\u00faltiples Elecciones
+sc.301=Movido permanentemente
+sc.302=Movido tempor\u00e1lmente
+sc.303=Mirar Otro
+sc.304=No Modificado
+sc.305=Usar Proxy
+sc.307=Redirecci\u00f3n Temporal
+sc.400=Petici\u00f3n incorrecta
+sc.401=No Autorizado
+sc.402=Pago requerido
+sc.403=Prohibido
+sc.404=No Encontrado
+sc.405=M\u00e9todo No Permitido
+sc.406=No Aceptable
+sc.407=Autentificaci\u00f3n Proxy Requerida
+sc.408=Request Caducada
+sc.409=Conflicto
+sc.410=Ido
+sc.411=Longitud Requerida
+sc.412=Precondici\u00f3n Fallada
+sc.413=Entidad de Request Demasiado Grande
+sc.414=Request-URI Demasiado Larga
+sc.415=Tipo de Medio No Soportado
+sc.416=El Rango Pedido No Ser Satisfecho
+sc.417=Expectativa Fallada
+sc.422=Entidad Improcesable
+sc.423=Bloqueado
+sc.424=Dependencia Fallida
+sc.500=Error Interno del Servidor
+sc.501=No Implementado
+sc.502=Pasarela Incorrecta
+sc.503=Servicio no Disponible
+sc.504=Pasarela Caducada
+sc.505=Versi\u00f3n de HTTP No Soportada
+sc.507=Almacenaje Insuficiente
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_fr.properties
new file mode 100644
index 0000000..79e57ca
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_fr.properties
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# HttpMessages. The values in this file will be used in HTTP headers and as such
+# may only contain TEXT as defined by RFC 2616
+sc.100=Continuer
+sc.101=Changement de Protocols
+sc.200=OK
+sc.201=Cr\u00e9e
+sc.202=Accept\u00e9
+sc.203=Information Sans-Autorit\u00e9
+sc.204=Pas de Contenu
+sc.205=Remise \u00e0 Z\u00e9ro de Contenu
+sc.206=Contenu Partiel
+sc.207=Etat Multiple
+sc.300=Choix Multiples
+sc.301=D\u00e9plac\u00e9 de fa\u00e7on Permanente
+sc.302=D\u00e9plac\u00e9 Temporairement
+sc.303=Voir Autre
+sc.304=Non Modifi\u00e9
+sc.305=Utilisation de Relais
+sc.307=Redirection Temporaire
+sc.400=Mauvaise Requ\u00eate
+sc.401=Non-Autoris\u00e9
+sc.402=Paiement N\u00e9cessaire
+sc.403=Interdit
+sc.404=Introuvable
+sc.405=M\u00e9thode Non Autoris\u00e9e
+sc.406=Inacceptable
+sc.407=Authentification de Relais N\u00e9cessaire
+sc.408=D\u00e9passement de D\u00e9lais pour la Requ\u00eate
+sc.409=Conflit
+sc.410=Parti
+sc.411=Taille Demand\u00e9e
+sc.412=Echec de Pr\u00e9-condition
+sc.413=Entit\u00e9 de Requ\u00eate Trop Grande
+sc.414=URI de Requ\u00eate Trop Grande
+sc.415=Type de Support Non Support\u00e9
+sc.416=Etendue de Requ\u00eate Irr\u00e9alisable
+sc.417=Echec d'Attente
+sc.422=Entit\u00e9 Ing\u00e9rable
+sc.424=Echec de D\u00e9pendance
+sc.500=Erreur Interne de Servlet
+sc.501=Non Impl\u00e9ment\u00e9
+sc.502=Mauvaise Passerelle
+sc.503=Service Indisponible
+sc.504=D\u00e9passement de D\u00e9lais pour la Passerelle
+sc.505=Version HTTP Non Support\u00e9e
+sc.507=Stockage Insuffisant
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_ja.properties
new file mode 100644
index 0000000..99234bc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/res/LocalStrings_ja.properties
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# HttpMessages. The values in this file will be used in HTTP headers and as such
+# may only contain TEXT as defined by RFC 2616. Since Japanese language messages
+# do not meet this requirement, English text is used.
+sc.100=Continue
+sc.101=Switching Protocols
+sc.200=OK
+sc.201=Created
+sc.202=Accepted
+sc.203=Non-Authoritative Information
+sc.204=No Content
+sc.205=Reset Content
+sc.206=Partial Content
+sc.207=Multi-Status
+sc.300=Multiple Choices
+sc.301=Moved Permanently
+sc.302=Moved Temporarily
+sc.303=See Other
+sc.304=Not Modified
+sc.305=Use Proxy
+sc.307=Temporary Redirect
+sc.400=Bad Request
+sc.401=Unauthorized
+sc.402=Payment Required
+sc.403=Forbidden
+sc.404=Not Found
+sc.405=Method Not Allowed
+sc.406=Not Acceptable
+sc.407=Proxy Authentication Required
+sc.408=Request Timeout
+sc.409=Conflict
+sc.410=Gone
+sc.411=Length Required
+sc.412=Precondition Failed
+sc.413=Request Entity Too Large
+sc.414=Request-URI Too Long
+sc.415=Unsupported Media Type
+sc.416=Requested Range Not Satisfiable
+sc.417=Expectation Failed
+sc.422=Unprocessable Entity
+sc.423=Locked
+sc.424=Failed Dependency
+sc.500=Internal Server Error
+sc.501=Not Implemented
+sc.502=Bad Gateway
+sc.503=Service Unavailable
+sc.504=Gateway Timeout
+sc.505=HTTP Version Not Supported
+sc.507=Insufficient Storage
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/log/CaptureLog.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/log/CaptureLog.java
new file mode 100644
index 0000000..5e5be43
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/log/CaptureLog.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+/**
+ * Per Thread System.err and System.out log capture data.
+ *
+ * @author Glenn L. Nielsen
+ */
+
+class CaptureLog {
+
+    protected CaptureLog() {
+        baos = new ByteArrayOutputStream();
+        ps = new PrintStream(baos);
+    }
+
+    private ByteArrayOutputStream baos;
+    private PrintStream ps;
+
+    protected PrintStream getStream() {
+        return ps;
+    }
+
+    protected void reset() {
+        baos.reset();
+    }
+
+    protected String getCapture() {
+        return baos.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/log/SystemLogHandler.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/log/SystemLogHandler.java
new file mode 100644
index 0000000..1549c52
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/log/SystemLogHandler.java
@@ -0,0 +1,272 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.log;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.EmptyStackException;
+import java.util.Stack;
+
+/**
+ * This helper class may be used to do sophisticated redirection of 
+ * System.out and System.err on a per Thread basis.
+ * 
+ * A stack is implemented per Thread so that nested startCapture
+ * and stopCapture can be used.
+ *
+ * @author Remy Maucherat
+ * @author Glenn L. Nielsen
+ */
+public class SystemLogHandler extends PrintStream {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct the handler to capture the output of the given steam.
+     */
+    public SystemLogHandler(PrintStream wrapped) {
+        super(wrapped);
+        out = wrapped;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Wrapped PrintStream.
+     */
+    protected PrintStream out = null;
+
+
+    /**
+     * Thread <-> CaptureLog associations.
+     */
+    protected static ThreadLocal<Stack<CaptureLog>> logs =
+        new ThreadLocal<Stack<CaptureLog>>();
+
+
+    /**
+     * Spare CaptureLog ready for reuse.
+     */
+    protected static Stack<CaptureLog> reuse = new Stack<CaptureLog>();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Start capturing thread's output.
+     */
+    public static void startCapture() {
+        CaptureLog log = null;
+        if (!reuse.isEmpty()) {
+            try {
+                log = reuse.pop();
+            } catch (EmptyStackException e) {
+                log = new CaptureLog();
+            }
+        } else {
+            log = new CaptureLog();
+        }
+        Stack<CaptureLog> stack = logs.get();
+        if (stack == null) {
+            stack = new Stack<CaptureLog>();
+            logs.set(stack);
+        }
+        stack.push(log);
+    }
+
+
+    /**
+     * Stop capturing thread's output and return captured data as a String.
+     */
+    public static String stopCapture() {
+        Stack<CaptureLog> stack = logs.get();
+        if (stack == null || stack.isEmpty()) {
+            return null;
+        }
+        CaptureLog log = stack.pop();
+        if (log == null) {
+            return null;
+        }
+        String capture = log.getCapture();
+        log.reset();
+        reuse.push(log);
+        return capture;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Find PrintStream to which the output must be written to.
+     */
+    protected PrintStream findStream() {
+        Stack<CaptureLog> stack = logs.get();
+        if (stack != null && !stack.isEmpty()) {
+            CaptureLog log = stack.peek();
+            if (log != null) {
+                PrintStream ps = log.getStream();
+                if (ps != null) {
+                    return ps;
+                }
+            }
+        }
+        return out;
+    }
+
+
+    // ---------------------------------------------------- PrintStream Methods
+
+
+    @Override
+    public void flush() {
+        findStream().flush();
+    }
+
+    @Override
+    public void close() {
+        findStream().close();
+    }
+
+    @Override
+    public boolean checkError() {
+        return findStream().checkError();
+    }
+
+    @Override
+    protected void setError() {
+        //findStream().setError();
+    }
+
+    @Override
+    public void write(int b) {
+        findStream().write(b);
+    }
+
+    @Override
+    public void write(byte[] b)
+        throws IOException {
+        findStream().write(b);
+    }
+
+    @Override
+    public void write(byte[] buf, int off, int len) {
+        findStream().write(buf, off, len);
+    }
+
+    @Override
+    public void print(boolean b) {
+        findStream().print(b);
+    }
+
+    @Override
+    public void print(char c) {
+        findStream().print(c);
+    }
+
+    @Override
+    public void print(int i) {
+        findStream().print(i);
+    }
+
+    @Override
+    public void print(long l) {
+        findStream().print(l);
+    }
+
+    @Override
+    public void print(float f) {
+        findStream().print(f);
+    }
+
+    @Override
+    public void print(double d) {
+        findStream().print(d);
+    }
+
+    @Override
+    public void print(char[] s) {
+        findStream().print(s);
+    }
+
+    @Override
+    public void print(String s) {
+        findStream().print(s);
+    }
+
+    @Override
+    public void print(Object obj) {
+        findStream().print(obj);
+    }
+
+    @Override
+    public void println() {
+        findStream().println();
+    }
+
+    @Override
+    public void println(boolean x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(char x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(int x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(long x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(float x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(double x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(char[] x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(String x) {
+        findStream().println(x);
+    }
+
+    @Override
+    public void println(Object x) {
+        findStream().println(x);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDOMSource.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDOMSource.java
new file mode 100644
index 0000000..69878e7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDOMSource.java
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.tomcat.util.modeler.modules;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.management.ObjectName;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.DomUtil;
+import org.apache.tomcat.util.modeler.AttributeInfo;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.NotificationInfo;
+import org.apache.tomcat.util.modeler.OperationInfo;
+import org.apache.tomcat.util.modeler.ParameterInfo;
+import org.apache.tomcat.util.modeler.Registry;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+
+public class MbeansDescriptorsDOMSource extends ModelerSource
+{
+    private static final Log log = LogFactory.getLog(MbeansDescriptorsDOMSource.class);
+
+    Registry registry;
+    String location;
+    String type;
+    Object source;
+    List<ObjectName> mbeans=new ArrayList<ObjectName>();
+
+    public void setRegistry(Registry reg) {
+        this.registry=reg;
+    }
+
+    public void setLocation( String loc ) {
+        this.location=loc;
+    }
+
+    /** Used if a single component is loaded
+     *
+     * @param type
+     */
+    public void setType( String type ) {
+       this.type=type;
+    }
+
+    public void setSource( Object source ) {
+        this.source=source;
+    }
+
+    @Override
+    public List<ObjectName> loadDescriptors( Registry registry, String location,
+                                 String type, Object source)
+            throws Exception
+    {
+        setRegistry(registry);
+        setLocation(location);
+        setType(type);
+        setSource(source);
+        execute();
+        return mbeans;
+    }
+
+    public void execute() throws Exception {
+        if( registry==null ) registry=Registry.getRegistry(null, null);
+
+        try {
+            InputStream stream=(InputStream)source;
+            long t1=System.currentTimeMillis();
+            Document doc=DomUtil.readXml(stream);
+            // Ignore for now the name of the root element
+            Node descriptorsN=doc.getDocumentElement();
+            //Node descriptorsN=DomUtil.getChild(doc, "mbeans-descriptors");
+            if( descriptorsN == null ) {
+                log.error("No descriptors found");
+                return;
+            }
+
+            Node firstMbeanN=null;
+            if( "mbean".equals( descriptorsN.getNodeName() ) ) {
+                firstMbeanN=descriptorsN;
+            } else {
+                firstMbeanN=DomUtil.getChild(descriptorsN, "mbean");
+            }
+
+            if( firstMbeanN==null ) {
+                log.error(" No mbean tags ");
+                return;
+            }
+
+            // Process each <mbean> element
+            for (Node mbeanN = firstMbeanN; mbeanN != null;
+                 mbeanN= DomUtil.getNext(mbeanN))
+            {
+
+                // Create a new managed bean info
+                ManagedBean managed=new ManagedBean();
+                DomUtil.setAttributes(managed, mbeanN);
+                Node firstN;
+
+                // Process descriptor subnode
+                /*Node mbeanDescriptorN =
+                    DomUtil.getChild(mbeanN, "descriptor");
+                if (mbeanDescriptorN != null) {
+                    Node firstFieldN =
+                        DomUtil.getChild(mbeanDescriptorN, "field");
+                    for (Node fieldN = firstFieldN; fieldN != null;
+                         fieldN = DomUtil.getNext(fieldN)) {
+                        FieldInfo fi = new FieldInfo();
+                        DomUtil.setAttributes(fi, fieldN);
+                        managed.addField(fi);
+                    }
+                }*/
+
+                // process attribute nodes
+                firstN=DomUtil.getChild( mbeanN, "attribute");
+                for (Node descN = firstN; descN != null;
+                     descN = DomUtil.getNext( descN ))
+                {
+
+                    // Create new attribute info
+                    AttributeInfo ai=new AttributeInfo();
+                    DomUtil.setAttributes(ai, descN);
+
+                    // Process descriptor subnode
+                    /*Node descriptorN =
+                        DomUtil.getChild(descN, "descriptor");
+                    if (descriptorN != null) {
+                        Node firstFieldN =
+                            DomUtil.getChild(descriptorN, "field");
+                        for (Node fieldN = firstFieldN; fieldN != null;
+                             fieldN = DomUtil.getNext(fieldN)) {
+                            FieldInfo fi = new FieldInfo();
+                            DomUtil.setAttributes(fi, fieldN);
+                            ai.addField(fi);
+                        }
+                    }
+                    */
+
+                    // Add this info to our managed bean info
+                    managed.addAttribute( ai );
+                    if (log.isTraceEnabled()) {
+                        log.trace("Create attribute " + ai);
+                    }
+
+                }
+
+                // process constructor nodes
+                /*
+                firstN=DomUtil.getChild( mbeanN, "constructor");
+                for (Node descN = firstN; descN != null;
+                     descN = DomUtil.getNext( descN )) {
+
+                    // Create new constructor info
+                    ConstructorInfo ci=new ConstructorInfo();
+                    DomUtil.setAttributes(ci, descN);
+
+                    // Process descriptor subnode
+                    Node firstDescriptorN =
+                        DomUtil.getChild(descN, "descriptor");
+                    if (firstDescriptorN != null) {
+                        Node firstFieldN =
+                            DomUtil.getChild(firstDescriptorN, "field");
+                        for (Node fieldN = firstFieldN; fieldN != null;
+                             fieldN = DomUtil.getNext(fieldN)) {
+                            FieldInfo fi = new FieldInfo();
+                            DomUtil.setAttributes(fi, fieldN);
+                            ci.addField(fi);
+                        }
+                    }
+
+                    // Process parameter subnodes
+                    Node firstParamN=DomUtil.getChild( descN, "parameter");
+                    for (Node paramN = firstParamN;  paramN != null;
+                         paramN = DomUtil.getNext(paramN))
+                    {
+                        ParameterInfo pi=new ParameterInfo();
+                        DomUtil.setAttributes(pi, paramN);
+                        ci.addParameter( pi );
+                    }
+
+                    // Add this info to our managed bean info
+                    managed.addConstructor( ci );
+                    if (log.isTraceEnabled()) {
+                        log.trace("Create constructor " + ci);
+                    }
+
+                }*/
+
+                // process notification nodes
+                firstN=DomUtil.getChild( mbeanN, "notification");
+                for (Node descN = firstN; descN != null;
+                     descN = DomUtil.getNext( descN ))
+                {
+
+                    // Create new notification info
+                    NotificationInfo ni=new NotificationInfo();
+                    DomUtil.setAttributes(ni, descN);
+
+                    // Process descriptor subnode
+                    /*Node firstDescriptorN =
+                        DomUtil.getChild(descN, "descriptor");
+                    if (firstDescriptorN != null) {
+                        Node firstFieldN =
+                            DomUtil.getChild(firstDescriptorN, "field");
+                        for (Node fieldN = firstFieldN; fieldN != null;
+                             fieldN = DomUtil.getNext(fieldN)) {
+                            FieldInfo fi = new FieldInfo();
+                            DomUtil.setAttributes(fi, fieldN);
+                            ni.addField(fi);
+                        }
+                    }*/
+
+                    // Process notification-type subnodes
+                    Node firstParamN=DomUtil.getChild( descN, "notification-type");
+                    for (Node paramN = firstParamN;  paramN != null;
+                         paramN = DomUtil.getNext(paramN))
+                    {
+                        ni.addNotifType( DomUtil.getContent(paramN) );
+                    }
+
+                    // Add this info to our managed bean info
+                    managed.addNotification( ni );
+                    if (log.isTraceEnabled()) {
+                        log.trace("Created notification " + ni);
+                    }
+
+                }
+
+                // process operation nodes
+                firstN=DomUtil.getChild( mbeanN, "operation");
+                for (Node descN = firstN; descN != null;
+                     descN = DomUtil.getNext( descN ))
+
+                {
+
+                    // Create new operation info
+                    OperationInfo oi=new OperationInfo();
+                    DomUtil.setAttributes(oi, descN);
+
+                    // Process descriptor subnode
+                    /*Node firstDescriptorN =
+                        DomUtil.getChild(descN, "descriptor");
+                    if (firstDescriptorN != null) {
+                        Node firstFieldN =
+                            DomUtil.getChild(firstDescriptorN, "field");
+                        for (Node fieldN = firstFieldN; fieldN != null;
+                             fieldN = DomUtil.getNext(fieldN)) {
+                            FieldInfo fi = new FieldInfo();
+                            DomUtil.setAttributes(fi, fieldN);
+                            oi.addField(fi);
+                        }
+                    }*/
+
+                    // Process parameter subnodes
+                    Node firstParamN=DomUtil.getChild( descN, "parameter");
+                    for (Node paramN = firstParamN;  paramN != null;
+                         paramN = DomUtil.getNext(paramN))
+                    {
+                        ParameterInfo pi=new ParameterInfo();
+                        DomUtil.setAttributes(pi, paramN);
+                        if( log.isTraceEnabled())
+                            log.trace("Add param " + pi.getName());
+                        oi.addParameter( pi );
+                    }
+
+                    // Add this info to our managed bean info
+                    managed.addOperation( oi );
+                    if( log.isTraceEnabled()) {
+                        log.trace("Create operation " + oi);
+                    }
+
+                }
+
+                // Add the completed managed bean info to the registry
+                registry.addManagedBean(managed);
+            }
+
+            long t2=System.currentTimeMillis();
+            log.debug( "Reading descriptors ( dom ) " + (t2-t1));
+        } catch( Exception ex ) {
+            log.error( "Error reading descriptors ", ex);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDigesterSource.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDigesterSource.java
new file mode 100644
index 0000000..4e8d858
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDigesterSource.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.tomcat.util.modeler.modules;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.management.ObjectName;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+public class MbeansDescriptorsDigesterSource extends ModelerSource
+{
+    private static final Log log =
+            LogFactory.getLog(MbeansDescriptorsDigesterSource.class);
+
+    Registry registry;
+    String location;
+    String type;
+    Object source;
+    List<ObjectName> mbeans = new ArrayList<ObjectName>();
+    protected static volatile Digester digester = null;
+    
+    protected static Digester createDigester(Registry registry) {
+
+        Digester digester = new Digester();
+        digester.setNamespaceAware(false);
+        digester.setValidating(false);
+        URL url = Registry.getRegistry(null, null).getClass().getResource
+            ("/org/apache/tomcat/util/modeler/mbeans-descriptors.dtd");
+        digester.register
+            ("-//Apache Software Foundation//DTD Model MBeans Configuration File",
+                url.toString());
+        
+        // Configure the parsing rules
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean",
+            "org.apache.tomcat.util.modeler.ManagedBean");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean",
+                "add",
+            "java.lang.Object");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/attribute",
+            "org.apache.tomcat.util.modeler.AttributeInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/attribute");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/attribute",
+                "addAttribute",
+            "org.apache.tomcat.util.modeler.AttributeInfo");
+        
+        /*digester.addObjectCreate
+            ("mbeans-descriptors/mbean/attribute/descriptor/field",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/attribute/descriptor/field");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/attribute/descriptor/field",
+                "addField",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/constructor",
+            "org.apache.tomcat.util.modeler.ConstructorInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/constructor");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/constructor",
+                "addConstructor",
+            "org.apache.tomcat.util.modeler.ConstructorInfo");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/constructor/descriptor/field",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/constructor/descriptor/field");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/constructor/descriptor/field",
+                "addField",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/constructor/parameter",
+            "org.apache.tomcat.util.modeler.ParameterInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/constructor/parameter");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/constructor/parameter",
+                "addParameter",
+            "org.apache.tomcat.util.modeler.ParameterInfo");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/descriptor/field",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/descriptor/field");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/descriptor/field",
+                "addField",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        */
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/notification",
+            "org.apache.tomcat.util.modeler.NotificationInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/notification");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/notification",
+                "addNotification",
+            "org.apache.tomcat.util.modeler.NotificationInfo");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/notification/descriptor/field",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/notification/descriptor/field");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/notification/descriptor/field",
+                "addField",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        
+        digester.addCallMethod
+            ("mbeans-descriptors/mbean/notification/notification-type",
+                "addNotifType", 0);
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/operation",
+            "org.apache.tomcat.util.modeler.OperationInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/operation");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/operation",
+                "addOperation",
+            "org.apache.tomcat.util.modeler.OperationInfo");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/operation/descriptor/field",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/operation/descriptor/field");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/operation/descriptor/field",
+                "addField",
+            "org.apache.tomcat.util.modeler.FieldInfo");
+        
+        digester.addObjectCreate
+            ("mbeans-descriptors/mbean/operation/parameter",
+            "org.apache.tomcat.util.modeler.ParameterInfo");
+        digester.addSetProperties
+            ("mbeans-descriptors/mbean/operation/parameter");
+        digester.addSetNext
+            ("mbeans-descriptors/mbean/operation/parameter",
+                "addParameter",
+            "org.apache.tomcat.util.modeler.ParameterInfo");
+        
+        return digester;
+        
+    }
+    
+    public void setRegistry(Registry reg) {
+        this.registry=reg;
+    }
+
+    public void setLocation( String loc ) {
+        this.location=loc;
+    }
+
+    /** Used if a single component is loaded
+     *
+     * @param type
+     */
+    public void setType( String type ) {
+       this.type=type;
+    }
+
+    public void setSource( Object source ) {
+        this.source=source;
+    }
+
+    @Override
+    public List<ObjectName> loadDescriptors( Registry registry, String location,
+            String type, Object source) throws Exception {
+        setRegistry(registry);
+        setLocation(location);
+        setType(type);
+        setSource(source);
+        execute();
+        return mbeans;
+    }
+
+    public void execute() throws Exception {
+        if (registry == null) {
+            registry = Registry.getRegistry(null, null);
+        }
+
+        InputStream stream = (InputStream) source;
+
+        if (digester == null) {
+            digester = createDigester(registry);
+        }
+        ArrayList<ManagedBean> loadedMbeans = new ArrayList<ManagedBean>();
+        
+        synchronized (digester) {
+            
+            // Process the input file to configure our registry
+            try {
+                // Push our registry object onto the stack
+                digester.push(loadedMbeans);
+                digester.parse(stream);
+            } catch (Exception e) {
+                log.error("Error digesting Registry data", e);
+                throw e;
+            } finally {
+                digester.reset();
+            }
+        
+        }
+        Iterator<ManagedBean> iter = loadedMbeans.iterator();
+        while (iter.hasNext()) {
+            registry.addManagedBean(iter.next());
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java
new file mode 100644
index 0000000..aba5607
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.util.modeler.modules;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.management.ObjectName;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.modeler.AttributeInfo;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.OperationInfo;
+import org.apache.tomcat.util.modeler.ParameterInfo;
+import org.apache.tomcat.util.modeler.Registry;
+
+public class MbeansDescriptorsIntrospectionSource extends ModelerSource
+{
+    private static final Log log = LogFactory.getLog(MbeansDescriptorsIntrospectionSource.class);
+
+    Registry registry;
+    String location;
+    String type;
+    Object source;
+    List<ObjectName> mbeans = new ArrayList<ObjectName>();
+
+    public void setRegistry(Registry reg) {
+        this.registry=reg;
+    }
+
+    public void setLocation( String loc ) {
+        this.location=loc;
+    }
+
+    /** Used if a single component is loaded
+     *
+     * @param type
+     */
+    public void setType( String type ) {
+       this.type=type;
+    }
+
+    public void setSource( Object source ) {
+        this.source=source;
+    }
+
+    @Override
+    public List<ObjectName> loadDescriptors( Registry registry, String location,
+            String type, Object source) throws Exception {
+        setRegistry(registry);
+        setLocation(location);
+        setType(type);
+        setSource(source);
+        execute();
+        return mbeans;
+    }
+
+    public void execute() throws Exception {
+        if( registry==null ) registry=Registry.getRegistry(null, null);
+        try {
+            ManagedBean managed = createManagedBean(registry, null,
+                    (Class<?>)source, type);
+            if( managed==null ) return;
+            managed.setName( type );
+
+            registry.addManagedBean(managed);
+
+        } catch( Exception ex ) {
+            log.error( "Error reading descriptors ", ex);
+        }
+    }
+
+
+
+    // ------------ Implementation for non-declared introspection classes
+
+    static Hashtable<String,String> specialMethods =
+        new Hashtable<String,String>();
+    static {
+        specialMethods.put( "preDeregister", "");
+        specialMethods.put( "postDeregister", "");
+    }
+
+    private static String strArray[]=new String[0];
+    private static ObjectName objNameArray[]=new ObjectName[0];
+    // createMBean == registerClass + registerMBean
+
+    private static Class<?>[] supportedTypes  = new Class[] {
+        Boolean.class,
+        Boolean.TYPE,
+        Byte.class,
+        Byte.TYPE,
+        Character.class,
+        Character.TYPE,
+        Short.class,
+        Short.TYPE,
+        Integer.class,
+        Integer.TYPE,
+        Long.class,
+        Long.TYPE,
+        Float.class, 
+        Float.TYPE,
+        Double.class,
+        Double.TYPE,
+        String.class,
+        strArray.getClass(),
+        BigDecimal.class,
+        BigInteger.class,
+        ObjectName.class,
+        objNameArray.getClass(),
+        java.io.File.class,
+    };
+    
+    /**
+     * Check if this class is one of the supported types.
+     * If the class is supported, returns true.  Otherwise,
+     * returns false.
+     * @param ret The class to check
+     * @return boolean True if class is supported
+     */ 
+    private boolean supportedType(Class<?> ret) {
+        for (int i = 0; i < supportedTypes.length; i++) {
+            if (ret == supportedTypes[i]) {
+                return true;
+            }
+        }
+        if (isBeanCompatible(ret)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if this class conforms to JavaBeans specifications.
+     * If the class is conformant, returns true.
+     *
+     * @param javaType The class to check
+     * @return boolean True if the class is compatible.
+     */
+    protected boolean isBeanCompatible(Class<?> javaType) {
+        // Must be a non-primitive and non array
+        if (javaType.isArray() || javaType.isPrimitive()) {
+            return false;
+        }
+
+        // Anything in the java or javax package that
+        // does not have a defined mapping is excluded.
+        if (javaType.getName().startsWith("java.") || 
+            javaType.getName().startsWith("javax.")) {
+            return false;
+        }
+
+        try {
+            javaType.getConstructor(new Class[]{});
+        } catch (java.lang.NoSuchMethodException e) {
+            return false;
+        }
+
+        // Make sure superclass is compatible
+        Class<?> superClass = javaType.getSuperclass();
+        if (superClass != null && 
+            superClass != java.lang.Object.class && 
+            superClass != java.lang.Exception.class && 
+            superClass != java.lang.Throwable.class) {
+            if (!isBeanCompatible(superClass)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /** 
+     * Process the methods and extract 'attributes', methods, etc
+     *
+     * @param realClass The class to process
+     * @param methods The methods to process
+     * @param attMap The attribute map (complete)
+     * @param getAttMap The readable attributes map
+     * @param setAttMap The settable attributes map
+     * @param invokeAttMap The invokable attributes map
+     */
+    private void initMethods(Class<?> realClass,
+                             Method methods[],
+                             Hashtable<String,Method> attMap,
+                             Hashtable<String,Method> getAttMap,
+                             Hashtable<String,Method> setAttMap,
+                             Hashtable<String,Method> invokeAttMap)
+    {
+        for (int j = 0; j < methods.length; ++j) {
+            String name=methods[j].getName();
+
+            if( Modifier.isStatic(methods[j].getModifiers()))
+                continue;
+            if( ! Modifier.isPublic( methods[j].getModifiers() ) ) {
+                if( log.isDebugEnabled())
+                    log.debug("Not public " + methods[j] );
+                continue;
+            }
+            if( methods[j].getDeclaringClass() == Object.class )
+                continue;
+            Class<?> params[] = methods[j].getParameterTypes();
+
+            if( name.startsWith( "get" ) && params.length==0) {
+                Class<?> ret = methods[j].getReturnType();
+                if( ! supportedType( ret ) ) {
+                    if( log.isDebugEnabled() )
+                        log.debug("Unsupported type " + methods[j]);
+                    continue;
+                }
+                name=unCapitalize( name.substring(3));
+
+                getAttMap.put( name, methods[j] );
+                // just a marker, we don't use the value
+                attMap.put( name, methods[j] );
+            } else if( name.startsWith( "is" ) && params.length==0) {
+                Class<?> ret = methods[j].getReturnType();
+                if( Boolean.TYPE != ret  ) {
+                    if( log.isDebugEnabled() )
+                        log.debug("Unsupported type " + methods[j] + " " + ret );
+                    continue;
+                }
+                name=unCapitalize( name.substring(2));
+
+                getAttMap.put( name, methods[j] );
+                // just a marker, we don't use the value
+                attMap.put( name, methods[j] );
+
+            } else if( name.startsWith( "set" ) && params.length==1) {
+                if( ! supportedType( params[0] ) ) {
+                    if( log.isDebugEnabled() )
+                        log.debug("Unsupported type " + methods[j] + " " + params[0]);
+                    continue;
+                }
+                name=unCapitalize( name.substring(3));
+                setAttMap.put( name, methods[j] );
+                attMap.put( name, methods[j] );
+            } else {
+                if( params.length == 0 ) {
+                    if( specialMethods.get( methods[j].getName() ) != null )
+                        continue;
+                    invokeAttMap.put( name, methods[j]);
+                } else {
+                    boolean supported=true;
+                    for( int i=0; i<params.length; i++ ) {
+                        if( ! supportedType( params[i])) {
+                            supported=false;
+                            break;
+                        }
+                    }
+                    if( supported )
+                        invokeAttMap.put( name, methods[j]);
+                }
+            }
+        }
+    }
+
+    /**
+     * XXX Find if the 'className' is the name of the MBean or
+     *       the real class ( I suppose first )
+     * XXX Read (optional) descriptions from a .properties, generated
+     *       from source
+     * XXX Deal with constructors
+     *
+     * @param registry The Bean registry (not used)
+     * @param domain The bean domain (not used)
+     * @param realClass The class to analyze
+     * @param type The bean type
+     * @return ManagedBean The create MBean
+     */
+    public ManagedBean createManagedBean(Registry registry, String domain,
+                                         Class<?> realClass, String type)
+    {
+        ManagedBean mbean= new ManagedBean();
+
+        Method methods[]=null;
+
+        Hashtable<String,Method> attMap = new Hashtable<String,Method>();
+        // key: attribute val: getter method
+        Hashtable<String,Method> getAttMap = new Hashtable<String,Method>();
+        // key: attribute val: setter method
+        Hashtable<String,Method> setAttMap = new Hashtable<String,Method>();
+        // key: operation val: invoke method
+        Hashtable<String,Method> invokeAttMap = new Hashtable<String,Method>();
+
+        methods = realClass.getMethods();
+
+        initMethods(realClass, methods, attMap, getAttMap, setAttMap, invokeAttMap );
+
+        try {
+
+            Enumeration<String> en = attMap.keys();
+            while( en.hasMoreElements() ) {
+                String name = en.nextElement();
+                AttributeInfo ai=new AttributeInfo();
+                ai.setName( name );
+                Method gm = getAttMap.get(name);
+                if( gm!=null ) {
+                    //ai.setGetMethodObj( gm );
+                    ai.setGetMethod( gm.getName());
+                    Class<?> t=gm.getReturnType();
+                    if( t!=null )
+                        ai.setType( t.getName() );
+                }
+                Method sm = setAttMap.get(name);
+                if( sm!=null ) {
+                    //ai.setSetMethodObj(sm);
+                    Class<?> t = sm.getParameterTypes()[0];
+                    if( t!=null )
+                        ai.setType( t.getName());
+                    ai.setSetMethod( sm.getName());
+                }
+                ai.setDescription("Introspected attribute " + name);
+                if( log.isDebugEnabled()) log.debug("Introspected attribute " +
+                        name + " " + gm + " " + sm);
+                if( gm==null )
+                    ai.setReadable(false);
+                if( sm==null )
+                    ai.setWriteable(false);
+                if( sm!=null || gm!=null )
+                    mbean.addAttribute(ai);
+            }
+
+            en=invokeAttMap.keys();
+            while( en.hasMoreElements() ) {
+                String name = en.nextElement();
+                Method m = invokeAttMap.get(name);
+                if( m!=null && name != null ) {
+                    OperationInfo op=new OperationInfo();
+                    op.setName(name);
+                    op.setReturnType(m.getReturnType().getName());
+                    op.setDescription("Introspected operation " + name);
+                    Class<?> parms[] = m.getParameterTypes();
+                    for(int i=0; i<parms.length; i++ ) {
+                        ParameterInfo pi=new ParameterInfo();
+                        pi.setType(parms[i].getName());
+                        pi.setName( "param" + i);
+                        pi.setDescription("Introspected parameter param" + i);
+                        op.addParameter(pi);
+                    }
+                    mbean.addOperation(op);
+                } else {
+                    log.error("Null arg " + name + " " + m );
+                }
+            }
+
+            /*Constructor[] constructors = realClass.getConstructors();
+            for(int i=0;i<constructors.length;i++) {
+                ConstructorInfo info = new ConstructorInfo();
+                String className = realClass.getName();
+                int nIndex = -1;
+                if((nIndex = className.lastIndexOf('.'))!=-1) {
+                    className = className.substring(nIndex+1);
+                }
+                info.setName(className);
+                info.setDescription(constructors[i].getName());
+                Class classes[] = constructors[i].getParameterTypes();
+                for(int j=0;j<classes.length;j++) {
+                    ParameterInfo pi = new ParameterInfo();
+                    pi.setType(classes[j].getName());
+                    pi.setName("param" + j);
+                    pi.setDescription("Introspected parameter param" + j);
+                    info.addParameter(pi);
+                }
+                mbean.addConstructor(info);
+            }
+            */
+            
+            if( log.isDebugEnabled())
+                log.debug("Setting name: " + type );
+            mbean.setName( type );
+
+            return mbean;
+        } catch( Exception ex ) {
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
+
+    // -------------------- Utils --------------------
+    /**
+     * Converts the first character of the given
+     * String into lower-case.
+     *
+     * @param name The string to convert
+     * @return String
+     */
+    private static String unCapitalize(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+        char chars[] = name.toCharArray();
+        chars[0] = Character.toLowerCase(chars[0]);
+        return new String(chars);
+    }
+
+}
+
+// End of class: MbeanDescriptorsIntrospectionSource
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsSerSource.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsSerSource.java
new file mode 100644
index 0000000..8d589d9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsSerSource.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.modeler.modules;
+
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.management.ObjectName;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+
+
+public class MbeansDescriptorsSerSource extends ModelerSource
+{
+    private static final Log log = LogFactory.getLog(MbeansDescriptorsSerSource.class);
+    Registry registry;
+    String location;
+    String type;
+    Object source;
+    List<ObjectName> mbeans=new ArrayList<ObjectName>();
+
+    public void setRegistry(Registry reg) {
+        this.registry=reg;
+    }
+
+    public void setLocation( String loc ) {
+        this.location=loc;
+    }
+
+    /** Used if a single component is loaded
+     *
+     * @param type
+     */
+    public void setType( String type ) {
+       this.type=type;
+    }
+
+    public void setSource( Object source ) {
+        this.source=source;
+    }
+
+    @Override
+    public List<ObjectName> loadDescriptors( Registry registry, String location,
+            String type, Object source) throws Exception {
+        setRegistry(registry);
+        setLocation(location);
+        setType(type);
+        setSource(source);
+        execute();
+        return mbeans;
+    }
+
+    public void execute() throws Exception {
+        if( registry==null ) registry=Registry.getRegistry(null, null);
+        long t1=System.currentTimeMillis();
+        try {
+            InputStream stream=null;
+            if( source instanceof URL ) {
+                stream=((URL)source).openStream();
+            }
+            if( source instanceof InputStream ) {
+                stream=(InputStream)source;
+            }
+            if( stream==null ) {
+                throw new Exception( "Can't process "+ source);
+            }
+            ObjectInputStream ois=new ObjectInputStream(stream);
+            Thread.currentThread().setContextClassLoader(ManagedBean.class.getClassLoader());
+            Object obj=ois.readObject();
+            //log.info("Reading " + obj);
+            ManagedBean beans[]=(ManagedBean[])obj;
+            // after all are read without error
+            for( int i=0; i<beans.length; i++ ) {
+                registry.addManagedBean(beans[i]);
+            }
+
+        } catch( Exception ex ) {
+            log.error( "Error reading descriptors " + source + " " +  ex.toString(),
+                    ex);
+            throw ex;
+        }
+        long t2=System.currentTimeMillis();
+        log.info( "Reading descriptors ( ser ) " + (t2-t1));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansSource.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansSource.java
new file mode 100644
index 0000000..0345caa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansSource.java
@@ -0,0 +1,368 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.modeler.modules;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.management.Attribute;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.loading.MLet;
+import javax.xml.transform.TransformerException;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.DomUtil;
+import org.apache.tomcat.util.modeler.AttributeInfo;
+import org.apache.tomcat.util.modeler.BaseModelMBean;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.Registry;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+
+/** This will create mbeans based on a config file.
+ *  The format is an extended version of MLET.
+ *
+ * Classloading. We don't support any explicit classloader tag. 
+ * A ClassLoader is just an mbean ( it can be the standard MLetMBean or
+ * a custom one ). 
+ * 
+ * XXX add a special attribute to reference the loader mbean,
+ * XXX figure out how to deal with private loaders
+ */
+public class MbeansSource extends ModelerSource implements MbeansSourceMBean
+{
+    private static final Log log = LogFactory.getLog(MbeansSource.class);
+    Registry registry;
+    String type;
+
+    // true if we are during the original loading
+    boolean loading=true;
+    List<ObjectName> mbeans = new ArrayList<ObjectName>();
+    static boolean loaderLoaded=false;
+    private Document document;
+    private HashMap<ObjectName,Node> object2Node =
+        new HashMap<ObjectName,Node>();
+
+    long lastUpdate;
+    long updateInterval=10000; // 10s
+
+    public void setRegistry(Registry reg) {
+        this.registry=reg;
+    }          
+
+    public void setLocation( String loc ) {
+        this.location=loc;
+    }
+
+    /** Used if a single component is loaded
+     *
+     * @param type
+     */
+    public void setType( String type ) {
+       this.type=type;
+    }
+
+    public void setSource( Object source ) {
+        this.source=source;
+    }
+
+    public Object getSource() {
+        return source;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+    
+    /** Return the list of mbeans created by this source.
+     *  It can be used to implement runtime services.
+     */
+    public List<ObjectName> getMBeans() {
+        return mbeans;
+    }
+
+    @Override
+    public List<ObjectName> loadDescriptors(Registry registry, String location,
+            String type, Object source) throws Exception {
+        setRegistry(registry);
+        setLocation(location);
+        setType(type);
+        setSource(source);
+        execute();
+        return mbeans;
+    }
+    
+    public void start() throws Exception {
+        registry.invoke(mbeans, "start", false);        
+    }
+
+    public void stop() throws Exception {
+        registry.invoke(mbeans, "stop", false);        
+    }
+    
+    public void init() throws Exception {
+        if( mbeans==null) execute();
+        if( registry==null ) registry=Registry.getRegistry(null, null);
+        
+        registry.invoke(mbeans, "init", false);
+    }
+    
+    public void destroy() throws Exception {
+        registry.invoke(mbeans, "destroy", false);                
+    }
+    
+    public void load() throws Exception {
+        execute(); // backward compat
+    }
+
+    public void execute() throws Exception {
+        if( registry==null ) registry=Registry.getRegistry(null, null);
+        try {
+            InputStream stream=getInputStream();
+            long t1=System.currentTimeMillis();
+            document = DomUtil.readXml(stream);
+
+            // We don't care what the root node is.
+            Node descriptorsN=document.getDocumentElement();
+
+            if( descriptorsN == null ) {
+                log.error("No descriptors found");
+                return;
+            }
+
+            Node firstMbeanN=DomUtil.getChild(descriptorsN, null);
+
+            if( firstMbeanN==null ) {
+                // maybe we have a single mlet
+                if( log.isDebugEnabled() )
+                    log.debug("No child " + descriptorsN);
+                firstMbeanN=descriptorsN;
+            }
+
+            MBeanServer server =
+                Registry.getRegistry(null, null).getMBeanServer();
+
+            // XXX Not very clean...  Just a workaround
+            if( ! loaderLoaded ) {
+                // Register a loader that will be find ant classes.
+                ObjectName defaultLoader= new ObjectName("modeler",
+                        "loader", "modeler");
+                MLet mlet=new MLet( new URL[0], this.getClass().getClassLoader());
+                server.registerMBean(mlet, defaultLoader);
+                loaderLoaded=true;
+            }
+        
+            // Process nodes
+            for (Node mbeanN = firstMbeanN; mbeanN != null;
+                 mbeanN= DomUtil.getNext(mbeanN, null, Node.ELEMENT_NODE))
+            {
+                String nodeName=mbeanN.getNodeName();
+
+                // mbean is the "official" name
+                if( "mbean".equals(nodeName) || "MLET".equals(nodeName) )
+                {
+                    String code=DomUtil.getAttribute( mbeanN, "code" );
+                    String objectName=DomUtil.getAttribute( mbeanN, "objectName" );
+                    if( objectName==null ) {
+                        objectName=DomUtil.getAttribute( mbeanN, "name" );
+                    }
+                    
+                    if( log.isDebugEnabled())
+                        log.debug( "Processing mbean objectName=" + objectName +
+                                " code=" + code);
+
+                    // args can be grouped in constructor or direct childs
+                    Node constructorN=DomUtil.getChild(mbeanN, "constructor");
+                    if( constructorN == null ) constructorN=mbeanN;
+
+                    processArg(constructorN);
+
+                    try {
+                        ObjectName oname=new ObjectName(objectName);
+                        if( ! server.isRegistered( oname )) {
+                            // We wrap everything in a model mbean.
+                            // XXX need to support "StandardMBeanDescriptorsSource"
+                            String modelMBean=BaseModelMBean.class.getName();                            
+                            server.createMBean(modelMBean, oname,
+                                    new Object[] { code, this},
+                                    new String[] { String.class.getName(),
+                                                  ModelerSource.class.getName() } 
+                                    );
+                            mbeans.add(oname);
+                        }
+                        object2Node.put( oname, mbeanN );
+                        // XXX Arguments, loader !!!
+                    } catch( Exception ex ) {
+                        log.error( "Error creating mbean " + objectName, ex);
+                    }
+
+                    Node firstAttN=DomUtil.getChild(mbeanN, "attribute");
+                    for (Node descN = firstAttN; descN != null;
+                         descN = DomUtil.getNext( descN ))
+                    {
+                        processAttribute(server, descN, objectName);
+                    }
+                } else if("jmx-operation".equals(nodeName) ) {
+                    String name=DomUtil.getAttribute(mbeanN, "objectName");
+                    if( name==null )
+                        name=DomUtil.getAttribute(mbeanN, "name");
+
+                    String operation=DomUtil.getAttribute(mbeanN, "operation");
+
+                    if( log.isDebugEnabled())
+                        log.debug( "Processing invoke objectName=" + name +
+                                " code=" + operation);
+                    try {
+                        ObjectName oname=new ObjectName(name);
+
+                        processArg( mbeanN );
+                        server.invoke( oname, operation, null, null);
+                    } catch (Exception e) {
+                        log.error( "Error in invoke " + name + " " + operation);
+                    }
+                }
+
+                ManagedBean managed=new ManagedBean();
+                DomUtil.setAttributes(managed, mbeanN);
+                Node firstN;
+
+                // process attribute info
+                firstN=DomUtil.getChild( mbeanN, "attribute");
+                for (Node descN = firstN; descN != null;
+                     descN = DomUtil.getNext( descN ))
+                {
+                    AttributeInfo ci=new AttributeInfo();
+                    DomUtil.setAttributes(ci, descN);
+                    managed.addAttribute( ci );
+                }
+
+            }
+
+            long t2=System.currentTimeMillis();
+            log.info( "Reading mbeans  " + (t2-t1));
+            loading=false;
+        } catch( Exception ex ) {
+            log.error( "Error reading mbeans ", ex);
+        }
+    }
+    
+    @Override
+    public void updateField( ObjectName oname, String name, 
+                             Object value )
+    {
+        if( loading ) return;
+        // nothing by default
+        //log.info( "XXX UpdateField " + oname + " " + name + " " + value);
+        Node n = object2Node.get(oname);
+        if( n == null ) {
+            log.info( "Node not found " + oname );
+            return;
+        }
+        Node attNode=DomUtil.findChildWithAtt(n, "attribute", "name", name);
+        if( attNode == null ) {
+            // found no existing attribute with this name
+            attNode=n.getOwnerDocument().createElement("attribute");
+            DomUtil.setAttribute(attNode, "name", name);
+            n.appendChild(attNode);
+        } 
+        String oldValue=DomUtil.getAttribute(attNode, "value");
+        if( oldValue != null ) {
+            // we'll convert all values to text content
+            DomUtil.removeAttribute( attNode, "value");
+        }
+        DomUtil.setText(attNode, value.toString());
+
+        //store();
+    }
+    
+    /** Store the mbeans. 
+     * XXX add a background thread to store it periodically 
+     */ 
+    public void save() {
+        // XXX customize no often than ( based on standard descriptor ), etc.
+        // It doesn't work very well if we call this on each set att - 
+        // the triger will work for the first att, but all others will be delayed
+        long time=System.currentTimeMillis();
+        if( location!=null &&
+                time - lastUpdate > updateInterval ) {
+            lastUpdate=time;
+            try {
+                FileOutputStream fos=new FileOutputStream(location);
+                DomUtil.writeXml(document, fos);
+            } catch (TransformerException e) {
+                log.error( "Error writing");
+            } catch (FileNotFoundException e) {
+                log.error( "Error writing" ,e );
+            }
+        }
+    }
+
+    private void processAttribute(MBeanServer server,
+                                  Node descN, String objectName ) {
+        String attName=DomUtil.getAttribute(descN, "name");
+        String value=DomUtil.getAttribute(descN, "value");
+        String type=null; // DomUtil.getAttribute(descN, "type");
+        if( value==null ) {
+            // The value may be specified as CDATA
+            value=DomUtil.getContent(descN);
+        }
+        try {
+            if( log.isDebugEnabled())
+                log.debug("Set attribute " + objectName + " " + attName +
+                        " " + value);
+            ObjectName oname=new ObjectName(objectName);
+            // find the type
+            if( type==null )
+                type=registry.getType(  oname, attName );
+
+            if( type==null ) {
+                log.info("Can't find attribute " + objectName + " " + attName );
+
+            } else {
+                Object valueO=registry.convertValue( type, value);
+                server.setAttribute(oname, new Attribute(attName, valueO));
+            }
+        } catch( Exception ex) {
+            log.error("Error processing attribute " + objectName + " " +
+                    attName + " " + value, ex);
+        }
+
+    }
+
+    private void processArg(Node mbeanN) {
+        Node firstArgN=DomUtil.getChild(mbeanN, "arg" );
+        // process all args
+        for (Node argN = firstArgN; argN != null;
+             argN = DomUtil.getNext( argN ))
+        {
+            DomUtil.getAttribute(argN, "type");
+            String value=DomUtil.getAttribute(argN, "value");
+            if( value==null ) {
+                // The value may be specified as CDATA
+                value=DomUtil.getContent(argN);
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansSourceMBean.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansSourceMBean.java
new file mode 100644
index 0000000..5f4fd81
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/MbeansSourceMBean.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.modeler.modules;
+
+import java.util.List;
+
+import javax.management.ObjectName;
+
+
+/**
+ * This mbean will load an extended mlet file ( similar in syntax with jboss ).
+ * It'll keep track of all attribute changes and update the file when attributes
+ * change. 
+ */
+public interface MbeansSourceMBean 
+{
+    /** Set the source to be used to load the mbeans
+     * 
+     * @param source File or URL
+     */ 
+    public void setSource( Object source );
+    
+    public Object getSource();
+    
+    /** Return the list of loaded mbeans names
+     * 
+     * @return List of ObjectName
+     */ 
+    public List<ObjectName> getMBeans();
+
+    /** Load the mbeans from the source. Called automatically on init() 
+     * 
+     * @throws Exception
+     */ 
+    public void load() throws Exception;
+    
+    /** Call the init method on all mbeans. Will call load if not done already
+     * 
+     * @throws Exception
+     */ 
+    public void init() throws Exception;
+
+    /** Save the file.
+     */ 
+    public void save();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/ModelerSource.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/ModelerSource.java
new file mode 100644
index 0000000..77bada4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/ModelerSource.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.modeler.modules;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.List;
+
+import javax.management.ObjectName;
+
+import org.apache.tomcat.util.modeler.Registry;
+
+/** Source for descriptor data. More sources can be added.
+ *
+ */
+public class ModelerSource {
+    protected Object source;
+    protected String location;
+
+    /** Load data, returns a list of items. 
+     * 
+     * @param registry
+     * @param location
+     * @param type
+     * @param source Introspected object or some other source
+     * @throws Exception
+     */ 
+    public List<ObjectName> loadDescriptors( Registry registry, String location,
+            String type, Object source) throws Exception {
+        // TODO
+        return null;
+    }
+    
+    /** Callback from the BaseMBean to notify that an attribute has changed.
+     * Can be used to implement persistence.
+     * 
+     * @param oname
+     * @param name
+     * @param value
+     */ 
+    public void updateField( ObjectName oname, String name, 
+                             Object value ) {
+        // nothing by default 
+    }
+
+    public void store() {
+        // nothing
+    }
+
+    protected InputStream getInputStream() throws IOException {
+        if( source instanceof URL ) {
+            URL url=(URL)source;
+            location=url.toString();
+            return url.openStream();
+        } else if( source instanceof File ) {
+            location=((File)source).getAbsolutePath();
+            return new FileInputStream((File)source);            
+        } else if( source instanceof String) {
+            location=(String)source;
+            return new FileInputStream((String)source);            
+        } else if( source instanceof InputStream ) {
+            return (InputStream)source;
+        } 
+        return null;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/package.html
new file mode 100644
index 0000000..5a1bb96
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/modules/package.html
@@ -0,0 +1,59 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<html>
+<head>
+<title>org.apache.commons.modeler.modules</title>
+</head>
+<body>
+<p>Implementation classes - should not be used directly. The API is not stable
+but eventually the code will be refactored as a collection of mbeans that will be useable 
+( more or less ) indepedently.</p>
+
+<p>The MbeanDescriptors* classes are used to extract metadata from different sources. They
+are result of few stages of refactoring - now they look very similar with ant tasks and are
+close to normal mbeans, with an execute() method. DOM, SER, Introspection and Dynamic mbean 
+will load metadata from the corresponding sources.
+</p>
+
+<p>MbeansSource will load an extended MLET file, similar with jboss. It is not completely
+implemented - only modeler mbeans and dynamic mbeans are loaded. The important characteristic
+is that all declared mbeans will be registered in the mbean server as model mbeans. For
+regular java classes, the description will be used to construct the model mbean. DynamicMbeans
+metadata will be converted to model mbean and the model mbean wrapper will be loaded.</p>
+
+<p>The goal of MbeansSource is to implement a simple persistence mechanism. Since all components
+are model mbeans, we can detect all changes. The source will be loaded as DOM and modifications
+will be made to the tree. The save() method will save the DOM tree - preserving all comments
+and having only the changes that are needed.</p>
+
+<p>There are few remaining issues. First, we need to use the persistence metadata to avoid
+saving transient fields ( we save an attribute when we detect a change - but we don't know
+if this attribute should be saved ). The solution is to use the persistence fields in the
+spec - with some reasonable defaults or patterns for introspection or backward compat.
+</p>
+
+<p>Another problem is implementing adding and removing components. In catalina, a 
+factory is used to create the components, and save will operate on all mbeans. 
+For creation we need to also use a factory - using the "Type" as a parameter. This
+will also work very well with Ant1.6 where we can use the component factory to 
+do a "natural" mapping ( i.e. mbeans can be treated as tasks, with attributes as
+task attributes ). The second part can be solve by either using a parameter on
+the factory method ( saveTo ? ), or by having a single mbeans source per domain.
+</p>
+
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/AbstractEndpoint.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/AbstractEndpoint.java
new file mode 100644
index 0000000..b31f3f3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -0,0 +1,773 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+import java.io.File;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.KeyManagerFactory;
+
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.util.threads.CounterLatch;
+import org.apache.tomcat.util.threads.ResizableExecutor;
+import org.apache.tomcat.util.threads.TaskQueue;
+import org.apache.tomcat.util.threads.TaskThreadFactory;
+import org.apache.tomcat.util.threads.ThreadPoolExecutor;
+/**
+ *
+ * @author fhanik
+ * @author Mladen Turk
+ * @author Remy Maucherat
+ */
+public abstract class AbstractEndpoint {
+
+    // -------------------------------------------------------------- Constants
+    protected static final StringManager sm = StringManager.getManager("org.apache.tomcat.util.net.res");
+
+    public static interface Handler {
+        /**
+         * Different types of socket states to react upon.
+         */
+        public enum SocketState {
+            OPEN, CLOSED, LONG, ASYNC_END
+        }
+        
+
+        /**
+         * Obtain the GlobalRequestProcessor associated with the handler.
+         */
+        public Object getGlobal();
+        
+        
+        /**
+         * Recycle resources associated with the handler.
+         */
+        public void recycle();
+    }
+
+    protected enum BindState {
+        UNBOUND, BOUND_ON_INIT, BOUND_ON_START
+    }
+
+    private static final int INITIAL_ERROR_DELAY = 50;
+    private static final int MAX_ERROR_DELAY = 1600;
+
+    // ----------------------------------------------------------------- Fields
+
+
+    /**
+     * Running state of the endpoint.
+     */
+    protected volatile boolean running = false;
+
+
+    /**
+     * Will be set to true whenever the endpoint is paused.
+     */
+    protected volatile boolean paused = false;
+
+    /**
+     * Are we using an internal executor
+     */
+    protected volatile boolean internalExecutor = false;
+
+    /**
+     * counter for nr of connections handled by an endpoint
+     */
+    private volatile CounterLatch connectionCounterLatch = null;
+
+    /**
+     * Socket properties
+     */
+    protected SocketProperties socketProperties = new SocketProperties();
+    public SocketProperties getSocketProperties() {
+        return socketProperties;
+    }
+
+
+    // ----------------------------------------------------------------- Properties
+
+    private int maxConnections = 10000;
+    public void setMaxConnections(int maxCon) { this.maxConnections = maxCon; }
+    public int  getMaxConnections() { return this.maxConnections; }
+    /**
+     * External Executor based thread pool.
+     */
+    private Executor executor = null;
+    public void setExecutor(Executor executor) {
+        this.executor = executor;
+        this.internalExecutor = (executor==null);
+    }
+    public Executor getExecutor() { return executor; }
+
+
+    /**
+     * Server socket port.
+     */
+    private int port;
+    public int getPort() { return port; }
+    public void setPort(int port ) { this.port=port; }
+
+
+    /**
+     * Address for the server socket.
+     */
+    private InetAddress address;
+    public InetAddress getAddress() { return address; }
+    public void setAddress(InetAddress address) { this.address = address; }
+
+    /**
+     * Allows the server developer to specify the backlog that
+     * should be used for server sockets. By default, this value
+     * is 100.
+     */
+    private int backlog = 100;
+    public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
+    public int getBacklog() { return backlog; }
+
+    /**
+     * Controls when the Endpoint binds the port. <code>true</code>, the default
+     * binds the port on {@link #init()} and unbinds it on {@link #destroy()}.
+     * If set to <code>false</code> the port is bound on {@link #start()} and
+     * unbound on {@link #stop()}.  
+     */
+    private boolean bindOnInit = true;
+    public boolean getBindOnInit() { return bindOnInit; }
+    public void setBindOnInit(boolean b) { this.bindOnInit = b; }
+    private BindState bindState = BindState.UNBOUND;
+
+    /**
+     * Keepalive timeout, if lesser or equal to 0 then soTimeout will be used.
+     */
+    private int keepAliveTimeout = -1;
+    public int getKeepAliveTimeout() { return keepAliveTimeout;}
+    public void setKeepAliveTimeout(int keepAliveTimeout) {
+        this.keepAliveTimeout = keepAliveTimeout;
+    }
+
+
+    /**
+     * Socket TCP no delay.
+     */
+    public boolean getTcpNoDelay() { return socketProperties.getTcpNoDelay();}
+    public void setTcpNoDelay(boolean tcpNoDelay) { socketProperties.setTcpNoDelay(tcpNoDelay); }
+
+
+    /**
+     * Socket linger.
+     */
+    public int getSoLinger() { return socketProperties.getSoLingerTime(); }
+    public void setSoLinger(int soLinger) {
+        socketProperties.setSoLingerTime(soLinger);
+        socketProperties.setSoLingerOn(soLinger>=0);
+    }
+
+
+    /**
+     * Socket timeout.
+     */
+    public int getSoTimeout() { return socketProperties.getSoTimeout(); }
+    public void setSoTimeout(int soTimeout) { socketProperties.setSoTimeout(soTimeout); }
+
+    /**
+     * SSL engine.
+     */
+    private boolean SSLEnabled = false;
+    public boolean isSSLEnabled() { return SSLEnabled; }
+    public void setSSLEnabled(boolean SSLEnabled) { this.SSLEnabled = SSLEnabled; }
+
+
+    private int minSpareThreads = 10;
+    public int getMinSpareThreads() {
+        return Math.min(minSpareThreads,getMaxThreads());
+    }
+    public void setMinSpareThreads(int minSpareThreads) {
+        this.minSpareThreads = minSpareThreads;
+        if (running && executor!=null) {
+            if (executor instanceof java.util.concurrent.ThreadPoolExecutor) {
+                ((java.util.concurrent.ThreadPoolExecutor)executor).setCorePoolSize(minSpareThreads);
+            } else if (executor instanceof ResizableExecutor) {
+                ((ResizableExecutor)executor).resizePool(minSpareThreads, maxThreads);
+            }
+        }
+    }
+
+    /**
+     * Maximum amount of worker threads.
+     */
+    private int maxThreads = 200;
+    public void setMaxThreads(int maxThreads) {
+        this.maxThreads = maxThreads;
+        if (running && executor!=null) {
+            if (executor instanceof java.util.concurrent.ThreadPoolExecutor) {
+                ((java.util.concurrent.ThreadPoolExecutor)executor).setMaximumPoolSize(maxThreads);
+            } else if (executor instanceof ResizableExecutor) {
+                ((ResizableExecutor)executor).resizePool(minSpareThreads, maxThreads);
+            }
+        }
+    }
+    public int getMaxThreads() {
+        if (running && executor!=null) {
+            if (executor instanceof java.util.concurrent.ThreadPoolExecutor) {
+                return ((java.util.concurrent.ThreadPoolExecutor)executor).getMaximumPoolSize();
+            } else if (executor instanceof ResizableExecutor) {
+                return ((ResizableExecutor)executor).getMaxThreads();
+            } else {
+                return -1;
+            }
+        } else {
+            return maxThreads;
+        }
+    }
+
+    /**
+     * Max keep alive requests
+     */
+    private int maxKeepAliveRequests=100; // as in Apache HTTPD server
+    public int getMaxKeepAliveRequests() {
+        return maxKeepAliveRequests;
+    }
+    public void setMaxKeepAliveRequests(int maxKeepAliveRequests) {
+        this.maxKeepAliveRequests = maxKeepAliveRequests;
+    }
+
+    /**
+     * Name of the thread pool, which will be used for naming child threads.
+     */
+    private String name = "TP";
+    public void setName(String name) { this.name = name; }
+    public String getName() { return name; }
+
+    /**
+     * The default is true - the created threads will be
+     *  in daemon mode. If set to false, the control thread
+     *  will not be daemon - and will keep the process alive.
+     */
+    private boolean daemon = true;
+    public void setDaemon(boolean b) { daemon = b; }
+    public boolean getDaemon() { return daemon; }
+
+
+    /**
+     * Priority of the worker threads.
+     */
+    protected int threadPriority = Thread.NORM_PRIORITY;
+    public void setThreadPriority(int threadPriority) { this.threadPriority = threadPriority; }
+    public int getThreadPriority() { return threadPriority; }
+
+    protected abstract boolean getDeferAccept();
+
+
+    /**
+     * Attributes provide a way for configuration to be passed to sub-components
+     * without the {@link org.apache.coyote.ProtocolHandler} being aware of the
+     * properties available on those sub-components. One example of such a
+     * sub-component is the
+     * {@link org.apache.tomcat.util.net.ServerSocketFactory}.
+     */
+    protected HashMap<String, Object> attributes =
+        new HashMap<String, Object>();
+    /** 
+     * Generic property setter called when a property for which a specific
+     * setter already exists within the
+     * {@link org.apache.coyote.ProtocolHandler} needs to be made available to
+     * sub-components. The specific setter will call this method to populate the
+     * attributes.
+     */
+    public void setAttribute(String name, Object value) {
+        if (getLog().isTraceEnabled()) {
+            getLog().trace(sm.getString("abstractProtocolHandler.setAttribute",
+                    name, value));
+        }
+        attributes.put(name, value);
+    }
+    /**
+     * Used by sub-components to retrieve configuration information.
+     */
+    public Object getAttribute(String key) {
+        Object value = attributes.get(key);
+        if (getLog().isTraceEnabled()) {
+            getLog().trace(sm.getString("abstractProtocolHandler.getAttribute",
+                    key, value));
+        }
+        return value;
+    }
+
+
+
+    public boolean setProperty(String name, String value) {
+        setAttribute(name, value);
+        final String socketName = "socket.";
+        try {
+            if (name.startsWith(socketName)) {
+                return IntrospectionUtils.setProperty(socketProperties, name.substring(socketName.length()), value);
+            } else {
+                return IntrospectionUtils.setProperty(this,name,value,false);
+            }
+        }catch ( Exception x ) {
+            getLog().error("Unable to set attribute \""+name+"\" to \""+value+"\"",x);
+            return false;
+        }
+    }
+    public String getProperty(String name) {
+        return (String) getAttribute(name);
+    }
+    
+    /**
+     * Return the amount of threads that are managed by the pool.
+     *
+     * @return the amount of threads that are managed by the pool
+     */
+    public int getCurrentThreadCount() {
+        if (executor!=null) {
+            if (executor instanceof ThreadPoolExecutor) {
+                return ((ThreadPoolExecutor)executor).getPoolSize();
+            } else if (executor instanceof ResizableExecutor) {
+                return ((ResizableExecutor)executor).getPoolSize();
+            } else {
+                return -1;
+            }
+        } else {
+            return -2;
+        }
+    }
+
+    /**
+     * Return the amount of threads that are in use
+     *
+     * @return the amount of threads that are in use
+     */
+    public int getCurrentThreadsBusy() {
+        if (executor!=null) {
+            if (executor instanceof ThreadPoolExecutor) {
+                return ((ThreadPoolExecutor)executor).getActiveCount();
+            } else if (executor instanceof ResizableExecutor) {
+                return ((ResizableExecutor)executor).getActiveCount();
+            } else {
+                return -1;
+            }
+        } else {
+            return -2;
+        }
+    }
+
+    public boolean isRunning() {
+        return running;
+    }
+
+    public boolean isPaused() {
+        return paused;
+    }
+
+
+    public void createExecutor() {
+        internalExecutor = true;
+        TaskQueue taskqueue = new TaskQueue();
+        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
+        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
+        taskqueue.setParent( (ThreadPoolExecutor) executor);
+    }
+
+    public void shutdownExecutor() {
+        if ( executor!=null && internalExecutor ) {
+            if ( executor instanceof ThreadPoolExecutor ) {
+                //this is our internal one, so we need to shut it down
+                ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
+                tpe.shutdownNow();
+                TaskQueue queue = (TaskQueue) tpe.getQueue();
+                queue.setParent(null);
+            }
+            executor = null;
+        }
+    }
+
+    /**
+     * Unlock the server socket accept using a bogus connection.
+     */
+    protected void unlockAccept() {
+        java.net.Socket s = null;
+        InetSocketAddress saddr = null;
+        try {
+            // Need to create a connection to unlock the accept();
+            if (address == null) {
+                saddr = new InetSocketAddress("localhost", getPort());
+            } else {
+                saddr = new InetSocketAddress(address,getPort());
+            }
+            s = new java.net.Socket();
+            int stmo = 2 * 1000;
+            int utmo = 2 * 1000;
+            if (getSocketProperties().getSoTimeout() > stmo)
+                stmo = getSocketProperties().getSoTimeout();
+            if (getSocketProperties().getUnlockTimeout() > utmo)
+                utmo = getSocketProperties().getUnlockTimeout();
+            s.setSoTimeout(stmo);
+            // TODO Consider hard-coding to s.setSoLinger(true,0)
+            s.setSoLinger(getSocketProperties().getSoLingerOn(),getSocketProperties().getSoLingerTime());
+            if (getLog().isDebugEnabled()) {
+                getLog().debug("About to unlock socket for:"+saddr);
+            }
+            s.connect(saddr,utmo);
+            if (getDeferAccept()) {
+                /*
+                 * In the case of a deferred accept / accept filters we need to
+                 * send data to wake up the accept. Send OPTIONS * to bypass
+                 * even BSD accept filters. The Acceptor will discard it.
+                 */
+                OutputStreamWriter sw;
+
+                sw = new OutputStreamWriter(s.getOutputStream(), "ISO-8859-1");
+                sw.write("OPTIONS * HTTP/1.0\r\n" +
+                         "User-Agent: Tomcat wakeup connection\r\n\r\n");
+                sw.flush();
+            }
+            if (getLog().isDebugEnabled()) {
+                getLog().debug("Socket unlock completed for:"+saddr);
+            }
+        } catch(Exception e) {
+            if (getLog().isDebugEnabled()) {
+                getLog().debug(sm.getString("endpoint.debug.unlock", "" + getPort()), e);
+            }
+        } finally {
+            if (s != null) {
+                try {
+                    s.close();
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------- Lifecycle methods
+
+    /*
+     * NOTE: There is no maintenance of state or checking for valid transitions
+     * within this class other than ensuring that bind/unbind are called in the
+     * right place. It is expected that the calling code will maintain state and
+     * prevent invalid state transitions.
+     */
+
+    public abstract void bind() throws Exception;
+    public abstract void unbind() throws Exception;
+    public abstract void startInternal() throws Exception;
+    public abstract void stopInternal() throws Exception;
+
+    public final void init() throws Exception {
+        if (bindOnInit) {
+            bind();
+            bindState = BindState.BOUND_ON_INIT;
+        }
+    }
+    
+    public final void start() throws Exception {
+        if (bindState == BindState.UNBOUND) {
+            bind();
+            bindState = BindState.BOUND_ON_START;
+        }
+        startInternal();
+    }
+
+    /**
+     * Pause the endpoint, which will stop it accepting new connections.
+     */
+    public void pause() {
+        if (running && !paused) {
+            paused = true;
+            unlockAccept();
+            // Heuristic: Sleep for a while to ensure pause of the endpoint
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+    }
+
+    /**
+     * Resume the endpoint, which will make it start accepting new connections
+     * again.
+     */
+    public void resume() {
+        if (running) {
+            paused = false;
+        }
+    }
+
+    public final void stop() throws Exception {
+        stopInternal();
+        if (bindState == BindState.BOUND_ON_START) {
+            unbind();
+            bindState = BindState.UNBOUND;
+        }
+    }
+
+    public final void destroy() throws Exception {
+        if (bindState == BindState.BOUND_ON_INIT) {
+            unbind();
+            bindState = BindState.UNBOUND;
+        }
+    }
+
+
+    public String adjustRelativePath(String path, String relativeTo) {
+        String newPath = path;
+        File f = new File(newPath);
+        if ( !f.isAbsolute()) {
+            newPath = relativeTo + File.separator + newPath;
+            f = new File(newPath);
+        }
+        if (!f.exists()) {
+            getLog().warn("configured file:["+newPath+"] does not exist.");
+        }
+        return newPath;
+    }
+
+    protected abstract Log getLog();
+    public abstract boolean getUseSendfile();
+    
+    protected CounterLatch initializeConnectionLatch() {
+        if (connectionCounterLatch==null) {
+            connectionCounterLatch = new CounterLatch(0,getMaxConnections());
+        }
+        return connectionCounterLatch;
+    }
+    
+    protected void releaseConnectionLatch() {
+        CounterLatch latch = connectionCounterLatch;
+        if (latch!=null) latch.releaseAll();
+        connectionCounterLatch = null;
+    }
+    
+    protected void awaitConnection() throws InterruptedException {
+        CounterLatch latch = connectionCounterLatch;
+        if (latch!=null) latch.await();
+    }
+    
+    protected long countUpConnection() {
+        CounterLatch latch = connectionCounterLatch;
+        if (latch!=null) return latch.countUp();
+        else return -1;
+    }
+    
+    protected long countDownConnection() {
+        CounterLatch latch = connectionCounterLatch;
+        if (latch!=null) {
+            long result = latch.countDown();
+            if (result<0) {
+                getLog().warn("Incorrect connection count, multiple socket.close called on the same socket." );
+            }
+            return result;
+        } else return -1;
+    }
+    
+    /**
+     * Provides a common approach for sub-classes to handle exceptions where a
+     * delay is required to prevent a Thread from entering a tight loop which
+     * will consume CPU and may also trigger large amounts of logging. For
+     * example, this can happen with the Acceptor thread if the ulimit for open
+     * files is reached.
+     * 
+     * @param currentErrorDelay The current delay beign applied on failure
+     * @return  The delay to apply on the next failure
+     */
+    protected int handleExceptionWithDelay(int currentErrorDelay) {
+        // Don't delay on first exception
+        if (currentErrorDelay > 0) {
+            try {
+                Thread.sleep(currentErrorDelay);
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+        
+        // On subsequent exceptions, start the delay at 50ms, doubling the delay
+        // on every subsequent exception until the delay reaches 1.6 seconds.
+        if (currentErrorDelay == 0) {
+            return INITIAL_ERROR_DELAY;
+        } else if (currentErrorDelay < MAX_ERROR_DELAY) {
+            return currentErrorDelay * 2;
+        } else {
+            return MAX_ERROR_DELAY;
+        }
+
+    }
+
+    // --------------------  SSL related properties --------------------
+
+    private String algorithm = KeyManagerFactory.getDefaultAlgorithm();
+    public String getAlgorithm() { return algorithm;}
+    public void setAlgorithm(String s ) { this.algorithm = s;}
+
+    private String clientAuth = "false";
+    public String getClientAuth() { return clientAuth;}
+    public void setClientAuth(String s ) { this.clientAuth = s;}
+
+    private String keystoreFile = System.getProperty("user.home")+"/.keystore";
+    public String getKeystoreFile() { return keystoreFile;}
+    public void setKeystoreFile(String s ) {
+        String file = adjustRelativePath(s,
+                System.getProperty(Constants.CATALINA_BASE_PROP));
+        this.keystoreFile = file;
+    }
+
+    private String keystorePass = null;
+    public String getKeystorePass() { return keystorePass;}
+    public void setKeystorePass(String s ) { this.keystorePass = s;}
+
+    private String keystoreType = "JKS";
+    public String getKeystoreType() { return keystoreType;}
+    public void setKeystoreType(String s ) { this.keystoreType = s;}
+
+    private String keystoreProvider = null;
+    public String getKeystoreProvider() { return keystoreProvider;}
+    public void setKeystoreProvider(String s ) { this.keystoreProvider = s;}
+
+    private String sslProtocol = "TLS";
+    public String getSslProtocol() { return sslProtocol;}
+    public void setSslProtocol(String s) { sslProtocol = s;}
+
+    // Note: Some implementations use the comma separated string, some use
+    // the array
+    private String ciphers = null;
+    private String[] ciphersarr = new String[0];
+    public String[] getCiphersArray() { return this.ciphersarr;}
+    public String getCiphers() { return ciphers;}
+    public void setCiphers(String s) {
+        ciphers = s;
+        if ( s == null ) ciphersarr = new String[0];
+        else {
+            StringTokenizer t = new StringTokenizer(s,",");
+            ciphersarr = new String[t.countTokens()];
+            for (int i=0; i<ciphersarr.length; i++ ) ciphersarr[i] = t.nextToken();
+        }
+    }
+
+    private String keyAlias = null;
+    public String getKeyAlias() { return keyAlias;}
+    public void setKeyAlias(String s ) { keyAlias = s;}
+
+    private String keyPass = null;
+    public String getKeyPass() { return keyPass;}
+    public void setKeyPass(String s ) { this.keyPass = s;}
+
+    private String truststoreFile = System.getProperty("javax.net.ssl.trustStore");
+    public String getTruststoreFile() {return truststoreFile;}
+    public void setTruststoreFile(String s) {
+        if (s == null) {
+            this.truststoreFile = null;
+        } else {
+            String file = adjustRelativePath(s,
+                    System.getProperty(Constants.CATALINA_BASE_PROP));
+            this.truststoreFile = file;
+        }
+    }
+
+    private String truststorePass =
+        System.getProperty("javax.net.ssl.trustStorePassword");
+    public String getTruststorePass() {return truststorePass;}
+    public void setTruststorePass(String truststorePass) {
+        this.truststorePass = truststorePass;
+    }
+
+    private String truststoreType =
+        System.getProperty("javax.net.ssl.trustStoreType");
+    public String getTruststoreType() {return truststoreType;}
+    public void setTruststoreType(String truststoreType) {
+        this.truststoreType = truststoreType;
+    }
+
+    private String truststoreProvider = null;
+    public String getTruststoreProvider() {return truststoreProvider;}
+    public void setTruststoreProvider(String truststoreProvider) {
+        this.truststoreProvider = truststoreProvider;
+    }
+
+    private String truststoreAlgorithm = null;
+    public String getTruststoreAlgorithm() {return truststoreAlgorithm;}
+    public void setTruststoreAlgorithm(String truststoreAlgorithm) {
+        this.truststoreAlgorithm = truststoreAlgorithm;
+    }
+
+    private String trustManagerClassName = null;
+    public String getTrustManagerClassName() {return trustManagerClassName;}
+    public void setTrustManagerClassName(String trustManagerClassName) {
+        this.trustManagerClassName = trustManagerClassName;
+    }
+
+    private String crlFile = null;
+    public String getCrlFile() {return crlFile;}
+    public void setCrlFile(String crlFile) {
+        this.crlFile = crlFile;
+    }
+
+    private String trustMaxCertLength = null;
+    public String getTrustMaxCertLength() {return trustMaxCertLength;}
+    public void setTrustMaxCertLength(String trustMaxCertLength) {
+        this.trustMaxCertLength = trustMaxCertLength;
+    }
+
+    private String sessionCacheSize = null;
+    public String getSessionCacheSize() { return sessionCacheSize;}
+    public void setSessionCacheSize(String s) { sessionCacheSize = s;}
+
+    private String sessionTimeout = "86400";
+    public String getSessionTimeout() { return sessionTimeout;}
+    public void setSessionTimeout(String s) { sessionTimeout = s;}
+
+    private String allowUnsafeLegacyRenegotiation = null;
+    public String getAllowUnsafeLegacyRenegotiation() {
+        return allowUnsafeLegacyRenegotiation;
+    }
+    public void setAllowUnsafeLegacyRenegotiation(String s) {
+        allowUnsafeLegacyRenegotiation = s;
+    }
+
+
+    private String[] sslEnabledProtocolsarr =  new String[0];
+    public String[] getSslEnabledProtocolsArray() {
+        return this.sslEnabledProtocolsarr;
+    }
+    public void setSslEnabledProtocols(String s) {
+        if (s == null) {
+            this.sslEnabledProtocolsarr = new String[0];
+        } else {
+            ArrayList<String> sslEnabledProtocols = new ArrayList<String>();
+            StringTokenizer t = new StringTokenizer(s,",");
+            while (t.hasMoreTokens()) {
+                String p = t.nextToken().trim();
+                if (p.length() > 0) {
+                    sslEnabledProtocols.add(p);
+                }
+            }
+            sslEnabledProtocolsarr = sslEnabledProtocols.toArray(
+                    new String[sslEnabledProtocols.size()]);
+        }
+    }
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/AprEndpoint.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/AprEndpoint.java
new file mode 100644
index 0000000..53dd024
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/AprEndpoint.java
@@ -0,0 +1,1785 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Address;
+import org.apache.tomcat.jni.Error;
+import org.apache.tomcat.jni.File;
+import org.apache.tomcat.jni.Library;
+import org.apache.tomcat.jni.OS;
+import org.apache.tomcat.jni.Poll;
+import org.apache.tomcat.jni.Pool;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLContext;
+import org.apache.tomcat.jni.SSLSocket;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * APR tailored thread pool, providing the following services:
+ * <ul>
+ * <li>Socket acceptor thread</li>
+ * <li>Socket poller thread</li>
+ * <li>Sendfile thread</li>
+ * <li>Worker threads pool</li>
+ * </ul>
+ *
+ * When switching to Java 5, there's an opportunity to use the virtual
+ * machine's thread pool.
+ *
+ * @author Mladen Turk
+ * @author Remy Maucherat
+ */
+public class AprEndpoint extends AbstractEndpoint {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    private static final Log log = LogFactory.getLog(AprEndpoint.class);
+
+    private static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+
+    // ----------------------------------------------------------------- Fields
+    /**
+     * Root APR memory pool.
+     */
+    protected long rootPool = 0;
+
+
+    /**
+     * Server socket "pointer".
+     */
+    protected long serverSock = 0;
+
+
+    /**
+     * APR memory pool for the server socket.
+     */
+    protected long serverSockPool = 0;
+
+
+    /**
+     * SSL context.
+     */
+    protected long sslContext = 0;
+
+
+    private Acceptor acceptors[] = null;
+
+
+    protected ConcurrentLinkedQueue<SocketWrapper<Long>> waitingRequests =
+        new ConcurrentLinkedQueue<SocketWrapper<Long>>();
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Defer accept.
+     */
+    protected boolean deferAccept = true;
+    public void setDeferAccept(boolean deferAccept) { this.deferAccept = deferAccept; }
+    @Override
+    public boolean getDeferAccept() { return deferAccept; }
+
+
+    /**
+     * Size of the socket poller.
+     */
+    protected int pollerSize = 8 * 1024;
+    public void setPollerSize(int pollerSize) { this.pollerSize = pollerSize; }
+    public int getPollerSize() { return pollerSize; }
+
+
+    /**
+     * Size of the sendfile (= concurrent files which can be served).
+     */
+    protected int sendfileSize = 1 * 1024;
+    public void setSendfileSize(int sendfileSize) { this.sendfileSize = sendfileSize; }
+    public int getSendfileSize() { return sendfileSize; }
+
+
+    /**
+     * Handling of accepted sockets.
+     */
+    protected Handler handler = null;
+    public void setHandler(Handler handler ) { this.handler = handler; }
+    public Handler getHandler() { return handler; }
+
+
+    /**
+     * Poll interval, in microseconds. The smaller the value, the more CPU the poller
+     * will use, but the more responsive to activity it will be.
+     */
+    protected int pollTime = 2000;
+    public int getPollTime() { return pollTime; }
+    public void setPollTime(int pollTime) { if (pollTime > 0) { this.pollTime = pollTime; } }
+
+
+    /**
+     * Use sendfile for sending static files.
+     */
+    protected boolean useSendfile = Library.APR_HAS_SENDFILE;
+    public void setUseSendfile(boolean useSendfile) { this.useSendfile = useSendfile; }
+    @Override
+    public boolean getUseSendfile() { return useSendfile; }
+
+
+    /**
+     * Allow comet request handling.
+     */
+    protected boolean useComet = true;
+    public void setUseComet(boolean useComet) { this.useComet = useComet; }
+    public boolean getUseComet() { return useComet; }
+
+
+    /**
+     * Acceptor thread count.
+     */
+    protected int acceptorThreadCount = 0;
+    public void setAcceptorThreadCount(int acceptorThreadCount) { this.acceptorThreadCount = acceptorThreadCount; }
+    public int getAcceptorThreadCount() { return acceptorThreadCount; }
+
+
+    /**
+     * Sendfile thread count.
+     */
+    protected int sendfileThreadCount = 0;
+    public void setSendfileThreadCount(int sendfileThreadCount) { this.sendfileThreadCount = sendfileThreadCount; }
+    public int getSendfileThreadCount() { return sendfileThreadCount; }
+
+
+    /**
+     * Poller thread count.
+     */
+    protected int pollerThreadCount = 0;
+    public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
+    public int getPollerThreadCount() { return pollerThreadCount; }
+
+
+    /**
+     * The socket poller.
+     */
+    protected Poller[] pollers = null;
+    protected int pollerRoundRobin = 0;
+    public Poller getPoller() {
+        pollerRoundRobin = (pollerRoundRobin + 1) % pollers.length;
+        return pollers[pollerRoundRobin];
+    }
+
+
+    /**
+     * The socket poller used for Comet support.
+     */
+    protected Poller[] cometPollers = null;
+    protected int cometPollerRoundRobin = 0;
+    public Poller getCometPoller() {
+        cometPollerRoundRobin = (cometPollerRoundRobin + 1) % cometPollers.length;
+        return cometPollers[cometPollerRoundRobin];
+    }
+
+
+    /**
+     * The static file sender.
+     */
+    protected Sendfile[] sendfiles = null;
+    protected int sendfileRoundRobin = 0;
+    public Sendfile getSendfile() {
+        sendfileRoundRobin = (sendfileRoundRobin + 1) % sendfiles.length;
+        return sendfiles[sendfileRoundRobin];
+    }
+
+
+    /**
+     * SSL protocols.
+     */
+    protected String SSLProtocol = "all";
+    public String getSSLProtocol() { return SSLProtocol; }
+    public void setSSLProtocol(String SSLProtocol) { this.SSLProtocol = SSLProtocol; }
+
+
+    /**
+     * SSL password (if a cert is encrypted, and no password has been provided, a callback
+     * will ask for a password).
+     */
+    protected String SSLPassword = null;
+    public String getSSLPassword() { return SSLPassword; }
+    public void setSSLPassword(String SSLPassword) { this.SSLPassword = SSLPassword; }
+
+
+    /**
+     * SSL cipher suite.
+     */
+    protected String SSLCipherSuite = "ALL";
+    public String getSSLCipherSuite() { return SSLCipherSuite; }
+    public void setSSLCipherSuite(String SSLCipherSuite) { this.SSLCipherSuite = SSLCipherSuite; }
+
+
+    /**
+     * SSL certificate file.
+     */
+    protected String SSLCertificateFile = null;
+    public String getSSLCertificateFile() { return SSLCertificateFile; }
+    public void setSSLCertificateFile(String SSLCertificateFile) { this.SSLCertificateFile = SSLCertificateFile; }
+
+
+    /**
+     * SSL certificate key file.
+     */
+    protected String SSLCertificateKeyFile = null;
+    public String getSSLCertificateKeyFile() { return SSLCertificateKeyFile; }
+    public void setSSLCertificateKeyFile(String SSLCertificateKeyFile) { this.SSLCertificateKeyFile = SSLCertificateKeyFile; }
+
+
+    /**
+     * SSL certificate chain file.
+     */
+    protected String SSLCertificateChainFile = null;
+    public String getSSLCertificateChainFile() { return SSLCertificateChainFile; }
+    public void setSSLCertificateChainFile(String SSLCertificateChainFile) { this.SSLCertificateChainFile = SSLCertificateChainFile; }
+
+
+    /**
+     * SSL CA certificate path.
+     */
+    protected String SSLCACertificatePath = null;
+    public String getSSLCACertificatePath() { return SSLCACertificatePath; }
+    public void setSSLCACertificatePath(String SSLCACertificatePath) { this.SSLCACertificatePath = SSLCACertificatePath; }
+
+
+    /**
+     * SSL CA certificate file.
+     */
+    protected String SSLCACertificateFile = null;
+    public String getSSLCACertificateFile() { return SSLCACertificateFile; }
+    public void setSSLCACertificateFile(String SSLCACertificateFile) { this.SSLCACertificateFile = SSLCACertificateFile; }
+
+
+    /**
+     * SSL CA revocation path.
+     */
+    protected String SSLCARevocationPath = null;
+    public String getSSLCARevocationPath() { return SSLCARevocationPath; }
+    public void setSSLCARevocationPath(String SSLCARevocationPath) { this.SSLCARevocationPath = SSLCARevocationPath; }
+
+
+    /**
+     * SSL CA revocation file.
+     */
+    protected String SSLCARevocationFile = null;
+    public String getSSLCARevocationFile() { return SSLCARevocationFile; }
+    public void setSSLCARevocationFile(String SSLCARevocationFile) { this.SSLCARevocationFile = SSLCARevocationFile; }
+
+
+    /**
+     * SSL verify client.
+     */
+    protected String SSLVerifyClient = "none";
+    public String getSSLVerifyClient() { return SSLVerifyClient; }
+    public void setSSLVerifyClient(String SSLVerifyClient) { this.SSLVerifyClient = SSLVerifyClient; }
+
+
+    /**
+     * SSL verify depth.
+     */
+    protected int SSLVerifyDepth = 10;
+    public int getSSLVerifyDepth() { return SSLVerifyDepth; }
+    public void setSSLVerifyDepth(int SSLVerifyDepth) { this.SSLVerifyDepth = SSLVerifyDepth; }
+
+
+    /**
+     * SSL allow insecure renegotiation for the the client that does not
+     * support the secure renegotiation.
+     */
+    protected boolean SSLInsecureRenegotiation = false;
+    public void setSSLInsecureRenegotiation(boolean SSLInsecureRenegotiation) { this.SSLInsecureRenegotiation = SSLInsecureRenegotiation; }
+    public boolean getSSLInsecureRenegotiation() { return SSLInsecureRenegotiation; }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Number of keepalive sockets.
+     */
+    public int getKeepAliveCount() {
+        if (pollers == null) {
+            return 0;
+        }
+
+        int keepAliveCount = 0;
+        for (int i = 0; i < pollers.length; i++) {
+            keepAliveCount += pollers[i].getKeepAliveCount();
+        }
+        return keepAliveCount;
+    }
+
+
+    /**
+     * Number of sendfile sockets.
+     */
+    public int getSendfileCount() {
+        if (sendfiles == null) {
+            return 0;
+        }
+
+        int sendfileCount = 0;
+        for (int i = 0; i < sendfiles.length; i++) {
+            sendfileCount += sendfiles[i].getSendfileCount();
+        }
+        return sendfileCount;
+    }
+
+
+    // ----------------------------------------------- Public Lifecycle Methods
+
+
+    /**
+     * Initialize the endpoint.
+     */
+    @Override
+    public void bind() throws Exception {
+
+        // Create the root APR memory pool
+        try {
+            rootPool = Pool.create(0);
+        } catch (UnsatisfiedLinkError e) {
+            throw new Exception(sm.getString("endpoint.init.notavail"));
+        }
+
+        // Create the pool for the server socket
+        serverSockPool = Pool.create(rootPool);
+        // Create the APR address that will be bound
+        String addressStr = null;
+        if (getAddress() != null) {
+            addressStr = getAddress().getHostAddress();
+        }
+        int family = Socket.APR_INET;
+        if (Library.APR_HAVE_IPV6) {
+            if (addressStr == null) {
+                if (!OS.IS_BSD && !OS.IS_WIN32 && !OS.IS_WIN64)
+                    family = Socket.APR_UNSPEC;
+            } else if (addressStr.indexOf(':') >= 0) {
+                family = Socket.APR_UNSPEC;
+            }
+         }
+
+        long inetAddress = Address.info(addressStr, family,
+                getPort(), 0, rootPool);
+        // Create the APR server socket
+        serverSock = Socket.create(Address.getInfo(inetAddress).family,
+                Socket.SOCK_STREAM,
+                Socket.APR_PROTO_TCP, rootPool);
+        if (OS.IS_UNIX) {
+            Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+        }
+        // Deal with the firewalls that tend to drop the inactive sockets
+        Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
+        // Bind the server socket
+        int ret = Socket.bind(serverSock, inetAddress);
+        if (ret != 0) {
+            throw new Exception(sm.getString("endpoint.init.bind", "" + ret, Error.strerror(ret)));
+        }
+        // Start listening on the server socket
+        ret = Socket.listen(serverSock, getBacklog());
+        if (ret != 0) {
+            throw new Exception(sm.getString("endpoint.init.listen", "" + ret, Error.strerror(ret)));
+        }
+        if (OS.IS_WIN32 || OS.IS_WIN64) {
+            // On Windows set the reuseaddr flag after the bind/listen
+            Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+        }
+
+        // Sendfile usage on systems which don't support it cause major problems
+        if (useSendfile && !Library.APR_HAS_SENDFILE) {
+            useSendfile = false;
+        }
+
+        // Initialize thread count defaults for acceptor, poller and sendfile
+        if (acceptorThreadCount == 0) {
+            // FIXME: Doesn't seem to work that well with multiple accept threads
+            acceptorThreadCount = 1;
+        }
+        if (pollerThreadCount == 0) {
+            if ((OS.IS_WIN32 || OS.IS_WIN64) && (pollerSize > 1024)) {
+                // The maximum per poller to get reasonable performance is 1024
+                pollerThreadCount = pollerSize / 1024;
+                // Adjust poller size so that it won't reach the limit
+                pollerSize = pollerSize - (pollerSize % 1024);
+            } else {
+                // No explicit poller size limitation
+                pollerThreadCount = 1;
+            }
+        }
+        if (sendfileThreadCount == 0) {
+            if ((OS.IS_WIN32 || OS.IS_WIN64) && (sendfileSize > 1024)) {
+                // The maximum per poller to get reasonable performance is 1024
+                sendfileThreadCount = sendfileSize / 1024;
+                // Adjust poller size so that it won't reach the limit
+                sendfileSize = sendfileSize - (sendfileSize % 1024);
+            } else {
+                // No explicit poller size limitation
+                // FIXME: Default to one per CPU ?
+                sendfileThreadCount = 1;
+            }
+        }
+
+        // Delay accepting of new connections until data is available
+        // Only Linux kernels 2.4 + have that implemented
+        // on other platforms this call is noop and will return APR_ENOTIMPL.
+        if (deferAccept) {
+            if (Socket.optSet(serverSock, Socket.APR_TCP_DEFER_ACCEPT, 1) == Status.APR_ENOTIMPL) {
+                deferAccept = false;
+            }
+        }
+
+        // Initialize SSL if needed
+        if (isSSLEnabled()) {
+
+            if (SSLCertificateFile == null) {
+                // This is required
+                throw new Exception(sm.getString("endpoint.apr.noSslCertFile"));
+            }
+
+            // SSL protocol
+            int value = SSL.SSL_PROTOCOL_ALL;
+            if ("SSLv2".equalsIgnoreCase(SSLProtocol)) {
+                value = SSL.SSL_PROTOCOL_SSLV2;
+            } else if ("SSLv3".equalsIgnoreCase(SSLProtocol)) {
+                value = SSL.SSL_PROTOCOL_SSLV3;
+            } else if ("TLSv1".equalsIgnoreCase(SSLProtocol)) {
+                value = SSL.SSL_PROTOCOL_TLSV1;
+            } else if ("SSLv2+SSLv3".equalsIgnoreCase(SSLProtocol)) {
+                value = SSL.SSL_PROTOCOL_SSLV2 | SSL.SSL_PROTOCOL_SSLV3;
+            }
+            // Create SSL Context
+            sslContext = SSLContext.make(rootPool, value, SSL.SSL_MODE_SERVER);
+            if (SSLInsecureRenegotiation) {
+                boolean legacyRenegSupported = false;
+                try {
+                    legacyRenegSupported = SSL.hasOp(SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
+                    if (legacyRenegSupported)
+                        SSLContext.setOptions(sslContext, SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
+                } catch (UnsatisfiedLinkError e) {
+                    // Ignore
+                }
+                if (!legacyRenegSupported) {
+                    // OpenSSL does not support unsafe legacy renegotiation.
+                    log.warn(sm.getString("endpoint.warn.noInsecureReneg",
+                                          SSL.versionString()));
+                }
+            }
+            // List the ciphers that the client is permitted to negotiate
+            SSLContext.setCipherSuite(sslContext, SSLCipherSuite);
+            // Load Server key and certificate
+            SSLContext.setCertificate(sslContext, SSLCertificateFile, SSLCertificateKeyFile, SSLPassword, SSL.SSL_AIDX_RSA);
+            // Set certificate chain file
+            SSLContext.setCertificateChainFile(sslContext, SSLCertificateChainFile, false);
+            // Support Client Certificates
+            SSLContext.setCACertificate(sslContext, SSLCACertificateFile, SSLCACertificatePath);
+            // Set revocation
+            SSLContext.setCARevocation(sslContext, SSLCARevocationFile, SSLCARevocationPath);
+            // Client certificate verification
+            value = SSL.SSL_CVERIFY_NONE;
+            if ("optional".equalsIgnoreCase(SSLVerifyClient)) {
+                value = SSL.SSL_CVERIFY_OPTIONAL;
+            } else if ("require".equalsIgnoreCase(SSLVerifyClient)) {
+                value = SSL.SSL_CVERIFY_REQUIRE;
+            } else if ("optionalNoCA".equalsIgnoreCase(SSLVerifyClient)) {
+                value = SSL.SSL_CVERIFY_OPTIONAL_NO_CA;
+            }
+            SSLContext.setVerify(sslContext, value, SSLVerifyDepth);
+            // For now, sendfile is not supported with SSL
+            useSendfile = false;
+        }
+    }
+
+
+    /**
+     * Start the APR endpoint, creating acceptor, poller and sendfile threads.
+     */
+    @Override
+    public void startInternal() throws Exception {
+
+        if (!running) {
+            running = true;
+            paused = false;
+
+            // Create worker collection
+            if (getExecutor() == null) {
+                createExecutor();
+            }
+            
+            initializeConnectionLatch();
+
+            // Start poller threads
+            pollers = new Poller[pollerThreadCount];
+            for (int i = 0; i < pollerThreadCount; i++) {
+                pollers[i] = new Poller(false);
+                pollers[i].init();
+                pollers[i].setName(getName() + "-Poller-" + i);
+                pollers[i].setPriority(threadPriority);
+                pollers[i].setDaemon(true);
+                pollers[i].start();
+            }
+
+            // Start comet poller threads
+            cometPollers = new Poller[pollerThreadCount];
+            for (int i = 0; i < pollerThreadCount; i++) {
+                cometPollers[i] = new Poller(true);
+                cometPollers[i].init();
+                cometPollers[i].setName(getName() + "-CometPoller-" + i);
+                cometPollers[i].setPriority(threadPriority);
+                cometPollers[i].setDaemon(true);
+                cometPollers[i].start();
+            }
+
+            // Start sendfile threads
+            if (useSendfile) {
+                sendfiles = new Sendfile[sendfileThreadCount];
+                for (int i = 0; i < sendfileThreadCount; i++) {
+                    sendfiles[i] = new Sendfile();
+                    sendfiles[i].init();
+                    sendfiles[i].setName(getName() + "-Sendfile-" + i);
+                    sendfiles[i].setPriority(threadPriority);
+                    sendfiles[i].setDaemon(true);
+                    sendfiles[i].start();
+                }
+            }
+
+            // Start acceptor threads
+            acceptors = new Acceptor[acceptorThreadCount];
+            for (int i = 0; i < acceptorThreadCount; i++) {
+                acceptors[i] = new Acceptor();
+                acceptors[i].setName(getName() + "-Acceptor-" + i);
+                acceptors[i].setPriority(threadPriority);
+                acceptors[i].setDaemon(getDaemon());
+                acceptors[i].start();
+            }
+
+            // Start async timeout thread
+            Thread timeoutThread = new Thread(new AsyncTimeout(),
+                    getName() + "-AsyncTimeout");
+            timeoutThread.setPriority(threadPriority);
+            timeoutThread.setDaemon(true);
+            timeoutThread.start();
+        }
+    }
+
+
+    /**
+     * Stop the endpoint. This will cause all processing threads to stop.
+     */
+    @Override
+    public void stopInternal() {
+        releaseConnectionLatch();
+        if (!paused) {
+            pause();
+        }
+        if (running) {
+            running = false;
+            unlockAccept();
+            for (int i = 0; i < acceptors.length; i++) {
+                long s = System.currentTimeMillis() + 30000;
+                while (acceptors[i].isAlive()) {
+                    try {
+                        acceptors[i].interrupt();
+                        acceptors[i].join(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                    if (System.currentTimeMillis() >= s) {
+                        log.warn(sm.getString("endpoint.warn.unlockAcceptorFailed",
+                                 acceptors[i].getName()));
+                        // If the Acceptor is still running force
+                        // the hard socket close.
+                        if (serverSock != 0) {
+                            Socket.shutdown(serverSock, Socket.APR_SHUTDOWN_READ);
+                            serverSock = 0;
+                        }
+                    }
+                }
+            }
+            for (int i = 0; i < pollers.length; i++) {
+                try {
+                    pollers[i].destroy();
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+            pollers = null;
+            for (int i = 0; i < cometPollers.length; i++) {
+                try {
+                    cometPollers[i].destroy();
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+            cometPollers = null;
+            if (useSendfile) {
+                for (int i = 0; i < sendfiles.length; i++) {
+                    try {
+                        sendfiles[i].destroy();
+                    } catch (Exception e) {
+                        // Ignore
+                    }
+                }
+                sendfiles = null;
+            }
+        }
+        shutdownExecutor();
+    }
+
+
+    /**
+     * Deallocate APR memory pools, and close server socket.
+     */
+    @Override
+    public void unbind() throws Exception {
+        if (running) {
+            stop();
+        }
+
+        // Destroy pool if it was initialised
+        if (serverSockPool != 0) {
+            Pool.destroy(serverSockPool);
+            serverSockPool = 0;
+        }
+
+        // Close server socket if it was initialised
+        if (serverSock != 0) {
+            Socket.close(serverSock);
+            serverSock = 0;
+        }
+
+        sslContext = 0;
+
+        // Close all APR memory pools and resources if initialised
+        if (rootPool != 0) {
+            Pool.destroy(rootPool);
+            rootPool = 0;
+        }
+
+        handler.recycle();
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Process the specified connection.
+     */
+    protected boolean setSocketOptions(long socket) {
+        // Process the connection
+        int step = 1;
+        try {
+
+            // 1: Set socket options: timeout, linger, etc
+            if (socketProperties.getSoLingerOn() && socketProperties.getSoLingerTime() >= 0)
+                Socket.optSet(socket, Socket.APR_SO_LINGER, socketProperties.getSoLingerTime());
+            if (socketProperties.getTcpNoDelay())
+                Socket.optSet(socket, Socket.APR_TCP_NODELAY, (socketProperties.getTcpNoDelay() ? 1 : 0));
+            if (socketProperties.getSoTimeout() > 0)
+                Socket.timeoutSet(socket, socketProperties.getSoTimeout() * 1000);
+
+            // 2: SSL handshake
+            step = 2;
+            if (sslContext != 0) {
+                SSLSocket.attach(sslContext, socket);
+                if (SSLSocket.handshake(socket) != 0) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("endpoint.err.handshake") + ": " + SSL.getLastError());
+                    }
+                    return false;
+                }
+            }
+
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            if (log.isDebugEnabled()) {
+                if (step == 2) {
+                    log.debug(sm.getString("endpoint.err.handshake"), t);
+                } else {
+                    log.debug(sm.getString("endpoint.err.unexpected"), t);
+                }
+            }
+            // Tell to close the socket
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * Allocate a new poller of the specified size.
+     */
+    protected long allocatePoller(int size, long pool, int timeout) {
+        try {
+            return Poll.create(size, pool, 0, timeout * 1000);
+        } catch (Error e) {
+            if (Status.APR_STATUS_IS_EINVAL(e.getError())) {
+                log.info(sm.getString("endpoint.poll.limitedpollsize", "" + size));
+                return 0;
+            } else {
+                log.error(sm.getString("endpoint.poll.initfail"), e);
+                return -1;
+            }
+        }
+    }
+
+
+    /**
+     * Process given socket.
+     */
+    protected boolean processSocketWithOptions(long socket) {
+        try {
+            // During shutdown, executor may be null - avoid NPE
+            if (running) {
+                SocketWrapper<Long> wrapper =
+                    new SocketWrapper<Long>(Long.valueOf(socket));
+                getExecutor().execute(new SocketWithOptionsProcessor(wrapper));
+            }
+        } catch (RejectedExecutionException x) {
+            log.warn("Socket processing request was rejected for:"+socket,x);
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This means we got an OOM or similar creating a thread, or that
+            // the pool and its queue are full
+            log.error(sm.getString("endpoint.process.fail"), t);
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * Process given socket.
+     */
+    protected boolean processSocket(long socket) {
+        try {
+            SocketWrapper<Long> wrapper =
+                new SocketWrapper<Long>(Long.valueOf(socket));
+            getExecutor().execute(new SocketProcessor(wrapper, null));
+        } catch (RejectedExecutionException x) {
+            log.warn("Socket processing request was rejected for:"+socket,x);
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This means we got an OOM or similar creating a thread, or that
+            // the pool and its queue are full
+            log.error(sm.getString("endpoint.process.fail"), t);
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * Process given socket for an event.
+     */
+    protected boolean processSocket(long socket, SocketStatus status) {
+        try {
+            if (status == SocketStatus.OPEN || status == SocketStatus.STOP ||
+                    status == SocketStatus.TIMEOUT) {
+                SocketWrapper<Long> wrapper =
+                    new SocketWrapper<Long>(Long.valueOf(socket));
+                SocketEventProcessor proc =
+                    new SocketEventProcessor(wrapper, status);
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                try {
+                    if (IS_SECURITY_ENABLED) {
+                        PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                                getClass().getClassLoader());
+                        AccessController.doPrivileged(pa);
+                    } else {
+                        Thread.currentThread().setContextClassLoader(
+                                getClass().getClassLoader());
+                    }
+                    getExecutor().execute(proc);
+                } finally {
+                    if (IS_SECURITY_ENABLED) {
+                        PrivilegedAction<Void> pa = new PrivilegedSetTccl(loader);
+                        AccessController.doPrivileged(pa);
+                    } else {
+                        Thread.currentThread().setContextClassLoader(loader);
+                    }
+                }            }
+        } catch (RejectedExecutionException x) {
+            log.warn("Socket processing request was rejected for:"+socket,x);
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This means we got an OOM or similar creating a thread, or that
+            // the pool and its queue are full
+            log.error(sm.getString("endpoint.process.fail"), t);
+            return false;
+        }
+        return true;
+    }
+
+    public boolean processSocketAsync(SocketWrapper<Long> socket,
+            SocketStatus status) {
+        try {
+            synchronized (socket) {
+                if (waitingRequests.remove(socket)) {
+                    SocketProcessor proc = new SocketProcessor(socket, status);
+                    ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                    try {
+                        if (IS_SECURITY_ENABLED) {
+                            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                                    getClass().getClassLoader());
+                            AccessController.doPrivileged(pa);
+                        } else {
+                            Thread.currentThread().setContextClassLoader(
+                                    getClass().getClassLoader());
+                        }
+                        getExecutor().execute(proc);
+                    } finally {
+                        if (IS_SECURITY_ENABLED) {
+                            PrivilegedAction<Void> pa = new PrivilegedSetTccl(loader);
+                            AccessController.doPrivileged(pa);
+                        } else {
+                            Thread.currentThread().setContextClassLoader(loader);
+                        }
+                    }
+                }
+            }
+        } catch (RejectedExecutionException x) {
+            log.warn("Socket processing request was rejected for:"+socket,x);
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This means we got an OOM or similar creating a thread, or that
+            // the pool and its queue are full
+            log.error(sm.getString("endpoint.process.fail"), t);
+            return false;
+        }
+        return true;
+    }
+
+    private void destroySocket(long socket)
+    {
+        if (running && socket != 0) {
+            // If not running the socket will be destroyed by
+            // parent pool or acceptor socket.
+            // In any case disable double free which would cause JVM core.
+            Socket.destroy(socket);
+            countDownConnection();
+        }
+    }
+
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+    // --------------------------------------------------- Acceptor Inner Class
+
+
+    /**
+     * Server socket acceptor thread.
+     */
+    protected class Acceptor extends Thread {
+
+        private final Log log = LogFactory.getLog(AprEndpoint.Acceptor.class);
+
+        /**
+         * The background thread that listens for incoming TCP/IP connections and
+         * hands them off to an appropriate processor.
+         */
+        @Override
+        public void run() {
+
+            int errorDelay = 0;
+
+            // Loop until we receive a shutdown command
+            while (running) {
+
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                try {
+                    //if we have reached max connections, wait
+                    awaitConnection();
+                    
+                    long socket = 0;
+                    try {
+                        // Accept the next incoming connection from the server
+                        // socket
+                        socket = Socket.accept(serverSock);
+                    } catch (Exception e) {
+                        // Introduce delay if necessary
+                        errorDelay = handleExceptionWithDelay(errorDelay);
+                        // re-throw
+                        throw e;
+                    }
+                    // Successful accept, reset the error delay
+                    errorDelay = 0;
+
+                    //increment socket count
+                    countUpConnection();
+                    /*
+                     * In the case of a deferred accept unlockAccept needs to
+                     * send data. This data will be rubbish, so destroy the
+                     * socket and don't process it.
+                     */
+                    if (deferAccept && (paused || !running)) {
+                        destroySocket(socket);
+                        continue;
+                    }
+                    // Hand this socket off to an appropriate processor
+                    if (!processSocketWithOptions(socket)) {
+                        // Close socket and pool right away
+                        destroySocket(socket);
+                    }
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    if (running) {
+                        String msg = sm.getString("endpoint.accept.fail");
+                        if (t instanceof Error) {
+                            Error e = (Error) t;
+                            if (e.getError() == 233) {
+                                // Not an error on HP-UX so log as a warning
+                                // so it can be filtered out on that platform
+                                // See bug 50273
+                                log.warn(msg, t);
+                            } else {
+                                log.error(msg, t);
+                            }
+                        } else {
+                                log.error(msg, t);
+                        }
+                    }
+                }
+
+                // The processor will recycle itself when it finishes
+
+            }
+
+        }
+
+    }
+
+
+    /**
+     * Async timeout thread
+     */
+    protected class AsyncTimeout implements Runnable {
+        /**
+         * The background thread that checks async requests and fires the
+         * timeout if there has been no activity.
+         */
+        @Override
+        public void run() {
+
+            // Loop until we receive a shutdown command
+            while (running) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+                long now = System.currentTimeMillis();
+                Iterator<SocketWrapper<Long>> sockets =
+                    waitingRequests.iterator();
+                while (sockets.hasNext()) {
+                    SocketWrapper<Long> socket = sockets.next();
+                    if (socket.async) {
+                        long access = socket.getLastAccess();
+                        if ((now-access)>socket.getTimeout()) {
+                            processSocketAsync(socket,SocketStatus.TIMEOUT);
+                        }
+                    }
+                }
+
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+            }
+        }
+    }
+
+
+    // ----------------------------------------------------- Poller Inner Class
+    /**
+     * Poller class.
+     */
+    public class Poller extends Thread {
+
+        protected long serverPollset = 0;
+        protected long pool = 0;
+        protected long[] desc;
+
+        protected long[] addS;
+        protected volatile int addCount = 0;
+
+        protected boolean comet = true;
+
+        protected volatile int keepAliveCount = 0;
+        public int getKeepAliveCount() { return keepAliveCount; }
+
+        public Poller(boolean comet) {
+            this.comet = comet;
+        }
+
+        /**
+         * Create the poller. With some versions of APR, the maximum poller size will
+         * be 62 (recompiling APR is necessary to remove this limitation).
+         */
+        protected void init() {
+            pool = Pool.create(serverSockPool);
+            int size = pollerSize / pollerThreadCount;
+            int timeout = getKeepAliveTimeout();
+            if (timeout <= 0) {
+                timeout = socketProperties.getSoTimeout();
+            }
+            serverPollset = allocatePoller(size, pool, timeout);
+            if (serverPollset == 0 && size > 1024) {
+                size = 1024;
+                serverPollset = allocatePoller(size, pool, timeout);
+            }
+            if (serverPollset == 0) {
+                size = 62;
+                serverPollset = allocatePoller(size, pool, timeout);
+            }
+            desc = new long[size * 2];
+            keepAliveCount = 0;
+            addS = new long[size];
+            addCount = 0;
+        }
+
+        /**
+         * Destroy the poller.
+         */
+        @Override
+        public void destroy() {
+            // Close all sockets in the add queue
+            for (int i = 0; i < addCount; i++) {
+                if (comet) {
+                    processSocket(addS[i], SocketStatus.STOP);
+                } else {
+                    destroySocket(addS[i]);
+                }
+            }
+            // Close all sockets still in the poller
+            int rv = Poll.pollset(serverPollset, desc);
+            if (rv > 0) {
+                for (int n = 0; n < rv; n++) {
+                    if (comet) {
+                        processSocket(desc[n*2+1], SocketStatus.STOP);
+                    } else {
+                        destroySocket(desc[n*2+1]);
+                    }
+                }
+            }
+            Pool.destroy(pool);
+            keepAliveCount = 0;
+            addCount = 0;
+            try {
+                while (this.isAlive()) {
+                    this.interrupt();
+                    this.join(1000);
+                }
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+
+        /**
+         * Add specified socket and associated pool to the poller. The socket will
+         * be added to a temporary array, and polled first after a maximum amount
+         * of time equal to pollTime (in most cases, latency will be much lower,
+         * however).
+         *
+         * @param socket to add to the poller
+         */
+        public void add(long socket) {
+            synchronized (this) {
+                // Add socket to the list. Newly added sockets will wait
+                // at most for pollTime before being polled
+                if (addCount >= addS.length) {
+                    // Can't do anything: close the socket right away
+                    if (comet) {
+                        processSocket(socket, SocketStatus.ERROR);
+                    } else {
+                        destroySocket(socket);
+                    }
+                    return;
+                }
+                addS[addCount] = socket;
+                addCount++;
+                this.notify();
+            }
+        }
+
+        /**
+         * The background thread that listens for incoming TCP/IP connections and
+         * hands them off to an appropriate processor.
+         */
+        @Override
+        public void run() {
+
+            long maintainTime = 0;
+            // Loop until we receive a shutdown command
+            while (running) {
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                if (keepAliveCount < 1 && addCount < 1) {
+                    synchronized (this) {
+                        while (keepAliveCount < 1 && addCount < 1 && running) {
+                            // Reset maintain time.
+                            maintainTime = 0;
+                            try {
+                                this.wait();
+                            } catch (InterruptedException e) {
+                                // Ignore
+                            }
+                        }
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                try {
+                    // Add sockets which are waiting to the poller
+                    if (addCount > 0) {
+                        synchronized (this) {
+                            int successCount = 0;
+                            try {
+                                for (int i = (addCount - 1); i >= 0; i--) {
+                                    int rv = Poll.add
+                                        (serverPollset, addS[i], Poll.APR_POLLIN);
+                                    if (rv == Status.APR_SUCCESS) {
+                                        successCount++;
+                                    } else {
+                                        // Can't do anything: close the socket right away
+                                        if (comet) {
+                                            processSocket(addS[i], SocketStatus.ERROR);
+                                        } else {
+                                            destroySocket(addS[i]);
+                                        }
+                                    }
+                                }
+                            } finally {
+                                keepAliveCount += successCount;
+                                addCount = 0;
+                            }
+                        }
+                    }
+
+                    maintainTime += pollTime;
+                    // Pool for the specified interval
+                    int rv = Poll.poll(serverPollset, pollTime, desc, true);
+                    if (rv > 0) {
+                        keepAliveCount -= rv;
+                        for (int n = 0; n < rv; n++) {
+                            // Check for failed sockets and hand this socket off to a worker
+                            if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
+                                    || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)
+                                    || (comet && (!processSocket(desc[n*2+1], SocketStatus.OPEN)))
+                                    || (!comet && (!processSocket(desc[n*2+1])))) {
+                                // Close socket and clear pool
+                                if (comet) {
+                                    processSocket(desc[n*2+1], SocketStatus.DISCONNECT);
+                                } else {
+                                    destroySocket(desc[n*2+1]);
+                                }
+                                continue;
+                            }
+                        }
+                    } else if (rv < 0) {
+                        int errn = -rv;
+                        /* Any non timeup or interrupted error is critical */
+                        if ((errn != Status.TIMEUP) && (errn != Status.EINTR)) {
+                            if (errn >  Status.APR_OS_START_USERERR) {
+                                errn -=  Status.APR_OS_START_USERERR;
+                            }
+                            log.error(sm.getString("endpoint.poll.fail", "" + errn, Error.strerror(errn)));
+                            // Handle poll critical failure
+                            synchronized (this) {
+                                destroy();
+                                init();
+                            }
+                            continue;
+                        }
+                    }
+                    if (socketProperties.getSoTimeout() > 0 && maintainTime > 1000000L && running) {
+                        rv = Poll.maintain(serverPollset, desc, true);
+                        maintainTime = 0;
+                        if (rv > 0) {
+                            keepAliveCount -= rv;
+                            for (int n = 0; n < rv; n++) {
+                                // Close socket and clear pool
+                                if (comet) {
+                                    processSocket(desc[n], SocketStatus.TIMEOUT);
+                                } else {
+                                    destroySocket(desc[n]);
+                                }
+                            }
+                        }
+                    }
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("endpoint.poll.error"), t);
+                }
+
+            }
+
+            synchronized (this) {
+                this.notifyAll();
+            }
+
+        }
+
+    }
+
+
+    // ----------------------------------------------------- Worker Inner Class
+
+
+    // ----------------------------------------------- SendfileData Inner Class
+
+
+    /**
+     * SendfileData class.
+     */
+    public static class SendfileData {
+        // File
+        public String fileName;
+        public long fd;
+        public long fdpool;
+        // Range information
+        public long start;
+        public long end;
+        // Socket and socket pool
+        public long socket;
+        // Position
+        public long pos;
+        // KeepAlive flag
+        public boolean keepAlive;
+    }
+
+
+    // --------------------------------------------------- Sendfile Inner Class
+
+
+    /**
+     * Sendfile class.
+     */
+    public class Sendfile extends Thread {
+
+        protected long sendfilePollset = 0;
+        protected long pool = 0;
+        protected long[] desc;
+        protected HashMap<Long, SendfileData> sendfileData;
+
+        protected volatile int sendfileCount;
+        public int getSendfileCount() { return sendfileCount; }
+
+        protected ArrayList<SendfileData> addS;
+        protected volatile int addCount;
+
+        /**
+         * Create the sendfile poller. With some versions of APR, the maximum poller size will
+         * be 62 (recompiling APR is necessary to remove this limitation).
+         */
+        protected void init() {
+            pool = Pool.create(serverSockPool);
+            int size = sendfileSize / sendfileThreadCount;
+            sendfilePollset = allocatePoller(size, pool, socketProperties.getSoTimeout());
+            if (sendfilePollset == 0 && size > 1024) {
+                size = 1024;
+                sendfilePollset = allocatePoller(size, pool, socketProperties.getSoTimeout());
+            }
+            if (sendfilePollset == 0) {
+                size = 62;
+                sendfilePollset = allocatePoller(size, pool, socketProperties.getSoTimeout());
+            }
+            desc = new long[size * 2];
+            sendfileData = new HashMap<Long, SendfileData>(size);
+            addS = new ArrayList<SendfileData>();
+            addCount = 0;
+        }
+
+        /**
+         * Destroy the poller.
+         */
+        @Override
+        public void destroy() {
+            // Close any socket remaining in the add queue
+            addCount = 0;
+            for (int i = (addS.size() - 1); i >= 0; i--) {
+                SendfileData data = addS.get(i);
+                destroySocket(data.socket);
+            }
+            addS.clear();
+            // Close all sockets still in the poller
+            int rv = Poll.pollset(sendfilePollset, desc);
+            if (rv > 0) {
+                for (int n = 0; n < rv; n++) {
+                    destroySocket(desc[n*2+1]);
+                }
+            }
+            Pool.destroy(pool);
+            sendfileData.clear();
+            try {
+                while (this.isAlive()) {
+                    this.interrupt();
+                    this.join(1000);
+                }
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+
+        /**
+         * Add the sendfile data to the sendfile poller. Note that in most cases,
+         * the initial non blocking calls to sendfile will return right away, and
+         * will be handled asynchronously inside the kernel. As a result,
+         * the poller will never be used.
+         *
+         * @param data containing the reference to the data which should be sent
+         * @return true if all the data has been sent right away, and false
+         *              otherwise
+         */
+        public boolean add(SendfileData data) {
+            // Initialize fd from data given
+            try {
+                data.fdpool = Socket.pool(data.socket);
+                data.fd = File.open
+                    (data.fileName, File.APR_FOPEN_READ
+                     | File.APR_FOPEN_SENDFILE_ENABLED | File.APR_FOPEN_BINARY,
+                     0, data.fdpool);
+                data.pos = data.start;
+                // Set the socket to nonblocking mode
+                Socket.timeoutSet(data.socket, 0);
+                while (true) {
+                    long nw = Socket.sendfilen(data.socket, data.fd,
+                                               data.pos, data.end - data.pos, 0);
+                    if (nw < 0) {
+                        if (!(-nw == Status.EAGAIN)) {
+                            destroySocket(data.socket);
+                            data.socket = 0;
+                            return false;
+                        } else {
+                            // Break the loop and add the socket to poller.
+                            break;
+                        }
+                    }
+
+                    data.pos = data.pos + nw;
+                    if (data.pos >= data.end) {
+                        // Entire file has been sent
+                        Pool.destroy(data.fdpool);
+                        // Set back socket to blocking mode
+                        Socket.timeoutSet(data.socket, socketProperties.getSoTimeout() * 1000);
+                        return true;
+                    }
+                }
+            } catch (Exception e) {
+                log.error(sm.getString("endpoint.sendfile.error"), e);
+                return false;
+            }
+            // Add socket to the list. Newly added sockets will wait
+            // at most for pollTime before being polled
+            synchronized (this) {
+                addS.add(data);
+                addCount++;
+                this.notify();
+            }
+            return false;
+        }
+
+        /**
+         * Remove socket from the poller.
+         *
+         * @param data the sendfile data which should be removed
+         */
+        protected void remove(SendfileData data) {
+            int rv = Poll.remove(sendfilePollset, data.socket);
+            if (rv == Status.APR_SUCCESS) {
+                sendfileCount--;
+            }
+            sendfileData.remove(Long.valueOf(data.socket));
+        }
+
+        /**
+         * The background thread that listens for incoming TCP/IP connections and
+         * hands them off to an appropriate processor.
+         */
+        @Override
+        public void run() {
+
+            long maintainTime = 0;
+            // Loop until we receive a shutdown command
+            while (running) {
+
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                if (sendfileCount < 1 && addCount < 1) {
+                    synchronized (this) {
+                        while (sendfileCount < 1 && addS.size() < 1 && running) {
+                            // Reset maintain time.
+                            maintainTime = 0;
+                            try {
+                                this.wait();
+                            } catch (InterruptedException e) {
+                                // Ignore
+                            }
+                        }
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                try {
+                    // Add socket to the poller
+                    if (addCount > 0) {
+                        synchronized (this) {
+                            int successCount = 0;
+                            try {
+                                for (int i = (addS.size() - 1); i >= 0; i--) {
+                                    SendfileData data = addS.get(i);
+                                    int rv = Poll.add(sendfilePollset, data.socket, Poll.APR_POLLOUT);
+                                    if (rv == Status.APR_SUCCESS) {
+                                        sendfileData.put(Long.valueOf(data.socket), data);
+                                        successCount++;
+                                    } else {
+                                        log.warn(sm.getString("endpoint.sendfile.addfail", "" + rv, Error.strerror(rv)));
+                                        // Can't do anything: close the socket right away
+                                        destroySocket(data.socket);
+                                    }
+                                }
+                            } finally {
+                                sendfileCount += successCount;
+                                addS.clear();
+                                addCount = 0;
+                            }
+                        }
+                    }
+
+                    maintainTime += pollTime;
+                    // Pool for the specified interval
+                    int rv = Poll.poll(sendfilePollset, pollTime, desc, false);
+                    if (rv > 0) {
+                        for (int n = 0; n < rv; n++) {
+                            // Get the sendfile state
+                            SendfileData state =
+                                sendfileData.get(Long.valueOf(desc[n*2+1]));
+                            // Problem events
+                            if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
+                                    || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)) {
+                                // Close socket and clear pool
+                                remove(state);
+                                // Destroy file descriptor pool, which should close the file
+                                // Close the socket, as the response would be incomplete
+                                destroySocket(state.socket);
+                                continue;
+                            }
+                            // Write some data using sendfile
+                            long nw = Socket.sendfilen(state.socket, state.fd,
+                                                       state.pos,
+                                                       state.end - state.pos, 0);
+                            if (nw < 0) {
+                                // Close socket and clear pool
+                                remove(state);
+                                // Close the socket, as the response would be incomplete
+                                // This will close the file too.
+                                destroySocket(state.socket);
+                                continue;
+                            }
+
+                            state.pos = state.pos + nw;
+                            if (state.pos >= state.end) {
+                                remove(state);
+                                if (state.keepAlive) {
+                                    // Destroy file descriptor pool, which should close the file
+                                    Pool.destroy(state.fdpool);
+                                    Socket.timeoutSet(state.socket, socketProperties.getSoTimeout() * 1000);
+                                    // If all done put the socket back in the poller for
+                                    // processing of further requests
+                                    getPoller().add(state.socket);
+                                } else {
+                                    // Close the socket since this is
+                                    // the end of not keep-alive request.
+                                    destroySocket(state.socket);
+                                }
+                            }
+                        }
+                    } else if (rv < 0) {
+                        int errn = -rv;
+                        /* Any non timeup or interrupted error is critical */
+                        if ((errn != Status.TIMEUP) && (errn != Status.EINTR)) {
+                            if (errn >  Status.APR_OS_START_USERERR) {
+                                errn -=  Status.APR_OS_START_USERERR;
+                            }
+                            log.error(sm.getString("endpoint.poll.fail", "" + errn, Error.strerror(errn)));
+                            // Handle poll critical failure
+                            synchronized (this) {
+                                destroy();
+                                init();
+                            }
+                            continue;
+                        }
+                    }
+                    // Call maintain for the sendfile poller
+                    if (socketProperties.getSoTimeout() > 0 && maintainTime > 1000000L && running) {
+                        rv = Poll.maintain(sendfilePollset, desc, true);
+                        maintainTime = 0;
+                        if (rv > 0) {
+                            for (int n = 0; n < rv; n++) {
+                                // Get the sendfile state
+                                SendfileData state = sendfileData.get(Long.valueOf(desc[n]));
+                                // Close socket and clear pool
+                                remove(state);
+                                // Destroy file descriptor pool, which should close the file
+                                // Close the socket, as the response would be incomplete
+                                destroySocket(state.socket);
+                            }
+                        }
+                    }
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("endpoint.poll.error"), t);
+                }
+            }
+
+            synchronized (this) {
+                this.notifyAll();
+            }
+
+        }
+
+    }
+
+
+    // ------------------------------------------------ Handler Inner Interface
+
+
+    /**
+     * Bare bones interface used for socket processing. Per thread data is to be
+     * stored in the ThreadWithAttributes extra folders, or alternately in
+     * thread local fields.
+     */
+    public interface Handler extends AbstractEndpoint.Handler {
+        public SocketState process(SocketWrapper<Long> socket);
+        public SocketState event(SocketWrapper<Long> socket,
+                SocketStatus status);
+        public SocketState asyncDispatch(SocketWrapper<Long> socket,
+                SocketStatus status);
+    }
+
+
+    // ---------------------------------------------- SocketProcessor Inner Class
+
+    /**
+     * This class is the equivalent of the Worker, but will simply use in an
+     * external Executor thread pool. This will also set the socket options
+     * and do the handshake.
+     */
+    protected class SocketWithOptionsProcessor implements Runnable {
+
+        protected SocketWrapper<Long> socket = null;
+
+
+        public SocketWithOptionsProcessor(SocketWrapper<Long> socket) {
+            this.socket = socket;
+        }
+
+        @Override
+        public void run() {
+
+            synchronized (socket) {
+                if (!deferAccept) {
+                    if (setSocketOptions(socket.getSocket().longValue())) {
+                        getPoller().add(socket.getSocket().longValue());
+                    } else {
+                        // Close socket and pool
+                        destroySocket(socket.getSocket().longValue());
+                        socket = null;
+                    }
+                } else {
+                    // Process the request from this socket
+                    if (!setSocketOptions(socket.getSocket().longValue())) {
+                        // Close socket and pool
+                        destroySocket(socket.getSocket().longValue());
+                        socket = null;
+                    }
+                    // Process the request from this socket
+                    Handler.SocketState state = handler.process(socket);
+                    if (state == Handler.SocketState.CLOSED) {
+                        // Close socket and pool
+                        destroySocket(socket.getSocket().longValue());
+                        socket = null;
+                    } else if (state == Handler.SocketState.LONG) {
+                        socket.access();
+                        if (socket.async) {
+                            waitingRequests.add(socket);
+                        }
+                    } else if (state == Handler.SocketState.ASYNC_END) {
+                        socket.access();
+                        SocketProcessor proc =
+                            new SocketProcessor(socket, SocketStatus.OPEN);
+                        getExecutor().execute(proc);
+                    }
+                }
+            }
+        }
+    }
+
+
+    // ---------------------------------------------- SocketProcessor Inner Class
+
+
+    /**
+     * This class is the equivalent of the Worker, but will simply use in an
+     * external Executor thread pool.
+     */
+    protected class SocketProcessor implements Runnable {
+
+        protected SocketWrapper<Long> socket = null;
+        protected SocketStatus status = null;
+
+        public SocketProcessor(SocketWrapper<Long> socket,
+                SocketStatus status) {
+            this.socket = socket;
+            this.status = status;
+        }
+
+        @Override
+        public void run() {
+            synchronized (socket) {
+                // Process the request from this socket
+                Handler.SocketState state = (status==null)?handler.process(socket):handler.asyncDispatch(socket, status);
+                if (state == Handler.SocketState.CLOSED) {
+                    // Close socket and pool
+                    destroySocket(socket.getSocket().longValue());
+                    socket = null;
+                } else if (state == Handler.SocketState.LONG) {
+                    socket.access();
+                    if (socket.async) {
+                        waitingRequests.add(socket);
+                    }
+                } else if (state == Handler.SocketState.ASYNC_END) {
+                    socket.access();
+                    SocketProcessor proc = new SocketProcessor(socket, SocketStatus.OPEN);
+                    getExecutor().execute(proc);
+                }
+            }
+        }
+    }
+
+
+    // --------------------------------------- SocketEventProcessor Inner Class
+
+
+    /**
+     * This class is the equivalent of the Worker, but will simply use in an
+     * external Executor thread pool.
+     */
+    protected class SocketEventProcessor implements Runnable {
+
+        protected SocketWrapper<Long> socket = null;
+        protected SocketStatus status = null;
+
+        public SocketEventProcessor(SocketWrapper<Long> socket,
+                SocketStatus status) {
+            this.socket = socket;
+            this.status = status;
+        }
+
+        @Override
+        public void run() {
+            synchronized (socket) {
+                // Process the request from this socket
+                Handler.SocketState state = handler.event(socket, status);
+                if (state == Handler.SocketState.CLOSED) {
+                    // Close socket and pool
+                    destroySocket(socket.getSocket().longValue());
+                    socket = null;
+                }
+            }
+        }
+    }
+
+    private static class PrivilegedSetTccl implements PrivilegedAction<Void> {
+
+        private ClassLoader cl;
+
+        PrivilegedSetTccl(ClassLoader cl) {
+            this.cl = cl;
+        }
+
+        @Override
+        public Void run() {
+            Thread.currentThread().setContextClassLoader(cl);
+            return null;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/Constants.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/Constants.java
new file mode 100644
index 0000000..67ed493
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/Constants.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+public class Constants {
+
+    /**
+     * Name of the system property containing
+     * the tomcat instance installation path
+     */
+    public static final String CATALINA_BASE_PROP = "catalina.base";
+
+
+    /**
+     * Has security been turned on?
+     */
+    public static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/DefaultServerSocketFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/DefaultServerSocketFactory.java
new file mode 100644
index 0000000..4ee222d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/DefaultServerSocketFactory.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * Default server socket factory. Doesn't do much except give us
+ * plain old server sockets.
+ *
+ * @author db@eng.sun.com
+ * @author Harish Prabandham
+ */
+public class DefaultServerSocketFactory implements ServerSocketFactory {
+
+    private AbstractEndpoint endpoint;
+
+    public DefaultServerSocketFactory(AbstractEndpoint endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    @Override
+    public ServerSocket createSocket (int port) throws IOException {
+        return  new ServerSocket (port);
+    }
+
+    @Override
+    public ServerSocket createSocket (int port, int backlog)
+            throws IOException {
+        return new ServerSocket (port, backlog);
+    }
+
+    @Override
+    public ServerSocket createSocket (int port, int backlog,
+            InetAddress ifAddress) throws IOException {
+        return new ServerSocket (port, backlog, ifAddress);
+    }
+
+    @Override
+    public Socket acceptSocket(ServerSocket socket) throws IOException {
+        return socket.accept();
+    }
+
+    @Override
+    public void handshake(Socket sock) throws IOException {
+        // NOOP
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/JIoEndpoint.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/JIoEndpoint.java
new file mode 100644
index 0000000..3e97b4e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/JIoEndpoint.java
@@ -0,0 +1,589 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+import java.io.IOException;
+import java.net.BindException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+
+
+/**
+ * Handle incoming TCP connections.
+ *
+ * This class implement a simple server model: one listener thread accepts on a socket and
+ * creates a new worker thread for each incoming connection.
+ *
+ * More advanced Endpoints will reuse the threads, use queues, etc.
+ *
+ * @author James Duncan Davidson
+ * @author Jason Hunter
+ * @author James Todd
+ * @author Costin Manolache
+ * @author Gal Shachor
+ * @author Yoav Shapira
+ * @author Remy Maucherat
+ */
+public class JIoEndpoint extends AbstractEndpoint {
+
+
+    // -------------------------------------------------------------- Constants
+
+    private static final Log log = LogFactory.getLog(JIoEndpoint.class);
+
+    // ----------------------------------------------------------------- Fields
+
+    /**
+     * Associated server socket.
+     */
+    protected ServerSocket serverSocket = null;
+    
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Acceptor thread count.
+     */
+    protected int acceptorThreadCount = 0;
+    public void setAcceptorThreadCount(int acceptorThreadCount) { this.acceptorThreadCount = acceptorThreadCount; }
+    public int getAcceptorThreadCount() { return acceptorThreadCount; }
+    
+    /**
+     * Handling of accepted sockets.
+     */
+    protected Handler handler = null;
+    public void setHandler(Handler handler ) { this.handler = handler; }
+    public Handler getHandler() { return handler; }
+
+    /**
+     * Server socket factory.
+     */
+    protected ServerSocketFactory serverSocketFactory = null;
+    public void setServerSocketFactory(ServerSocketFactory factory) { this.serverSocketFactory = factory; }
+    public ServerSocketFactory getServerSocketFactory() { return serverSocketFactory; }
+
+
+    /**
+     * Is sendfile available
+     */
+    @Override
+    public boolean getUseSendfile() {
+        // Not supported
+        return false;
+    }
+
+
+    /**
+     * Is deferAccept supported?
+     */
+    @Override
+    public boolean getDeferAccept() {
+        // Not supported
+        return false;
+    }
+    
+    
+
+
+    // ------------------------------------------------ Handler Inner Interface
+
+    /**
+     * Bare bones interface used for socket processing. Per thread data is to be
+     * stored in the ThreadWithAttributes extra folders, or alternately in
+     * thread local fields.
+     */
+    public interface Handler extends AbstractEndpoint.Handler {
+        public SocketState process(SocketWrapper<Socket> socket);
+        public SocketState process(SocketWrapper<Socket> socket,
+                SocketStatus status);
+        public SSLImplementation getSslImplementation(); 
+    }
+
+
+    /**
+     * Async timeout thread
+     */
+    protected class AsyncTimeout implements Runnable {
+        /**
+         * The background thread that checks async requests and fires the
+         * timeout if there has been no activity.
+         */
+        @Override
+        public void run() {
+
+            // Loop until we receive a shutdown command
+            while (running) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+                long now = System.currentTimeMillis();
+                Iterator<SocketWrapper<Socket>> sockets =
+                    waitingRequests.iterator();
+                while (sockets.hasNext()) {
+                    SocketWrapper<Socket> socket = sockets.next();
+                    long access = socket.getLastAccess();
+                    if ((now-access)>socket.getTimeout()) {
+                        processSocketAsync(socket,SocketStatus.TIMEOUT);
+                    }
+                }
+                
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+                
+            }
+        }
+    }
+
+    
+    // --------------------------------------------------- Acceptor Inner Class
+    /**
+     * Server socket acceptor thread.
+     */
+    protected class Acceptor implements Runnable {
+
+
+        /**
+         * The background thread that listens for incoming TCP/IP connections and
+         * hands them off to an appropriate processor.
+         */
+        @Override
+        public void run() {
+
+            int errorDelay = 0;
+
+            // Loop until we receive a shutdown command
+            while (running) {
+
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                try {
+                    //if we have reached max connections, wait
+                    awaitConnection();
+
+                    Socket socket = null;
+                    try {
+                        // Accept the next incoming connection from the server
+                        // socket
+                        socket = serverSocketFactory.acceptSocket(serverSocket);
+                    } catch (IOException ioe) {
+                        // Introduce delay if necessary
+                        errorDelay = handleExceptionWithDelay(errorDelay);
+                        // re-throw
+                        throw ioe;
+                    }
+                    // Successful accept, reset the error delay
+                    errorDelay = 0;
+
+                    // Configure the socket
+                    if (setSocketOptions(socket)) {
+                        // Hand this socket off to an appropriate processor
+                        if (!processSocket(socket)) {
+                            // Close socket right away
+                            try {
+                                socket.close();
+                            } catch (IOException e) {
+                                // Ignore
+                            }
+                        } else {
+                            countUpConnection();
+                        }
+                    } else {
+                        // Close socket right away
+                        try {
+                            socket.close();
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                } catch (IOException x) {
+                    if (running) {
+                        log.error(sm.getString("endpoint.accept.fail"), x);
+                    }
+                } catch (NullPointerException npe) {
+                    if (running) {
+                        log.error(sm.getString("endpoint.accept.fail"), npe);
+                    }
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("endpoint.accept.fail"), t);
+                }
+                // The processor will recycle itself when it finishes
+            }
+        }
+    }
+
+
+    // ------------------------------------------- SocketProcessor Inner Class
+
+
+    /**
+     * This class is the equivalent of the Worker, but will simply use in an
+     * external Executor thread pool.
+     */
+    protected class SocketProcessor implements Runnable {
+        
+        protected SocketWrapper<Socket> socket = null;
+        protected SocketStatus status = null;
+        
+        public SocketProcessor(SocketWrapper<Socket> socket) {
+            if (socket==null) throw new NullPointerException();
+            this.socket = socket;
+        }
+
+        public SocketProcessor(SocketWrapper<Socket> socket, SocketStatus status) {
+            this(socket);
+            this.status = status;
+        }
+
+        @Override
+        public void run() {
+            boolean launch = false;
+            synchronized (socket) {
+                try {
+                    SocketState state = SocketState.OPEN;
+
+                    try {
+                        // SSL handshake
+                        serverSocketFactory.handshake(socket.getSocket());
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                        if (log.isDebugEnabled()) {
+                            log.debug(sm.getString("endpoint.err.handshake"), t);
+                        }
+                        // Tell to close the socket
+                        state = SocketState.CLOSED;
+                    }
+                        
+                    if ( (state != SocketState.CLOSED) ) {
+                        state = (status==null)?handler.process(socket):handler.process(socket,status);
+                    }
+                    if (state == SocketState.CLOSED) {
+                        // Close socket
+                        if (log.isTraceEnabled()) {
+                            log.trace("Closing socket:"+socket);
+                        }
+                        countDownConnection();
+                        try {
+                            socket.getSocket().close();
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    } else if (state == SocketState.ASYNC_END ||
+                            state == SocketState.OPEN){
+                        socket.setKeptAlive(true);
+                        socket.access();
+                        launch = true;
+                    } else if (state == SocketState.LONG) {
+                        socket.access();
+                        waitingRequests.add(socket);
+                    }
+                } finally {
+                    if (launch) {
+                        try {
+                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
+                        } catch (NullPointerException npe) {
+                            if (running) {
+                                log.error(sm.getString("endpoint.launch.fail"),
+                                        npe);
+                            }
+                        }
+                    }
+                }
+            }
+            socket = null;
+            // Finish up this request
+        }
+        
+    }
+
+
+    // -------------------- Public methods --------------------
+
+    @Override
+    public void bind() throws Exception {
+
+        // Initialize thread count defaults for acceptor
+        if (acceptorThreadCount == 0) {
+            acceptorThreadCount = 1;
+        }
+        if (serverSocketFactory == null) {
+            if (isSSLEnabled()) {
+                serverSocketFactory =
+                    handler.getSslImplementation().getServerSocketFactory(this);
+            } else {
+                serverSocketFactory = new DefaultServerSocketFactory(this);
+            }
+        }
+
+        if (serverSocket == null) {
+            try {
+                if (getAddress() == null) {
+                    serverSocket = serverSocketFactory.createSocket(getPort(),
+                            getBacklog());
+                } else {
+                    serverSocket = serverSocketFactory.createSocket(getPort(),
+                            getBacklog(), getAddress());
+                }
+            } catch (BindException orig) {
+                String msg;
+                if (getAddress() == null)
+                    msg = orig.getMessage() + " <null>:" + getPort();
+                else
+                    msg = orig.getMessage() + " " +
+                            getAddress().toString() + ":" + getPort();
+                BindException be = new BindException(msg);
+                be.initCause(orig);
+                throw be;
+            }
+        }
+        
+    }
+    
+    @Override
+    public void startInternal() throws Exception {
+
+        if (!running) {
+            running = true;
+            paused = false;
+
+            // Create worker collection
+            if (getExecutor() == null) {
+                createExecutor();
+            }
+            
+            initializeConnectionLatch();
+
+            // Start acceptor threads
+            for (int i = 0; i < acceptorThreadCount; i++) {
+                Thread acceptorThread = new Thread(new Acceptor(),
+                        getName() + "-Acceptor-" + i);
+                acceptorThread.setPriority(threadPriority);
+                acceptorThread.setDaemon(getDaemon());
+                acceptorThread.start();
+            }
+            
+            // Start async timeout thread
+            Thread timeoutThread = new Thread(new AsyncTimeout(),
+                    getName() + "-AsyncTimeout");
+            timeoutThread.setPriority(threadPriority);
+            timeoutThread.setDaemon(true);
+            timeoutThread.start();
+        }
+    }
+
+    @Override
+    public void stopInternal() {
+        releaseConnectionLatch();
+        if (!paused) {
+            pause();
+        }
+        if (running) {
+            running = false;
+            unlockAccept();
+        }
+        shutdownExecutor();
+    }
+
+    /**
+     * Deallocate APR memory pools, and close server socket.
+     */
+    @Override
+    public void unbind() throws Exception {
+        if (running) {
+            stop();
+        }
+        if (serverSocket != null) {
+            try {
+                if (serverSocket != null)
+                    serverSocket.close();
+            } catch (Exception e) {
+                log.error(sm.getString("endpoint.err.close"), e);
+            }
+            serverSocket = null;
+        }
+        handler.recycle();
+    }
+
+
+    /**
+     * Configure the socket.
+     */
+    protected boolean setSocketOptions(Socket socket) {
+        try {
+            // 1: Set socket options: timeout, linger, etc
+            socketProperties.setProperties(socket);
+        } catch (SocketException s) {
+            //error here is common if the client has reset the connection
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("endpoint.err.unexpected"), s);
+            }
+            // Close the socket
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("endpoint.err.unexpected"), t);
+            // Close the socket
+            return false;
+        }
+        return true;
+    }
+
+    
+    /**
+     * Process a new connection from a new client. Wraps the socket so
+     * keep-alive and other attributes can be tracked and then passes the socket
+     * to the executor for processing.
+     * 
+     * @param socket    The socket associated with the client.
+     * 
+     * @return          <code>true</code> if the socket is passed to the
+     *                  executor, <code>false</code> if something went wrong or
+     *                  if the endpoint is shutting down. Returning
+     *                  <code>false</code> is an indication to close the socket
+     *                  immediately.
+     */
+    protected boolean processSocket(Socket socket) {
+        // Process the request from this socket
+        try {
+            SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
+            wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
+            // During shutdown, executor may be null - avoid NPE
+            if (!running) {
+                return false;
+            }
+            getExecutor().execute(new SocketProcessor(wrapper));
+        } catch (RejectedExecutionException x) {
+            log.warn("Socket processing request was rejected for:"+socket,x);
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This means we got an OOM or similar creating a thread, or that
+            // the pool and its queue are full
+            log.error(sm.getString("endpoint.process.fail"), t);
+            return false;
+        }
+        return true;
+    }
+    
+    
+    /**
+     * Process an existing async connection. If processing is required, passes
+     * the wrapped socket to an executor for processing.
+     * 
+     * @param socket    The socket associated with the client.
+     * @param status    Only OPEN and TIMEOUT are used. The others are used for
+     *                  Comet requests that are not supported by the BIO (JIO)
+     *                  Connector.
+     * @return          <code>true</code> if the socket is passed to the
+     *                  executor, <code>false</code> if something went wrong.
+     *                  Returning <code>false</code> is an indication to close
+     *                  the socket immediately.
+     */
+    public boolean processSocketAsync(SocketWrapper<Socket> socket,
+            SocketStatus status) {
+        try {
+            if (waitingRequests.remove(socket)) {
+                SocketProcessor proc = new SocketProcessor(socket,status);
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                try {
+                    //threads should not be created by the webapp classloader
+                    if (Constants.IS_SECURITY_ENABLED) {
+                        PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                                getClass().getClassLoader());
+                        AccessController.doPrivileged(pa);
+                    } else {
+                        Thread.currentThread().setContextClassLoader(
+                                getClass().getClassLoader());
+                    }
+                    // During shutdown, executor may be null - avoid NPE
+                    if (!running) {
+                        return false;
+                    }
+                    getExecutor().execute(proc);
+                }finally {
+                    if (Constants.IS_SECURITY_ENABLED) {
+                        PrivilegedAction<Void> pa = new PrivilegedSetTccl(loader);
+                        AccessController.doPrivileged(pa);
+                    } else {
+                        Thread.currentThread().setContextClassLoader(loader);
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This means we got an OOM or similar creating a thread, or that
+            // the pool and its queue are full
+            log.error(sm.getString("endpoint.process.fail"), t);
+            return false;
+        }
+        return true;
+    }
+
+    protected ConcurrentLinkedQueue<SocketWrapper<Socket>> waitingRequests =
+        new ConcurrentLinkedQueue<SocketWrapper<Socket>>();
+
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+    private static class PrivilegedSetTccl implements PrivilegedAction<Void> {
+
+        private ClassLoader cl;
+
+        PrivilegedSetTccl(ClassLoader cl) {
+            this.cl = cl;
+        }
+
+        @Override
+        public Void run() {
+            Thread.currentThread().setContextClassLoader(cl);
+            return null;
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioBlockingSelector.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioBlockingSelector.java
new file mode 100644
index 0000000..192120b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioBlockingSelector.java
@@ -0,0 +1,399 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.MutableInteger;
+import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
+
+public class NioBlockingSelector {
+    
+    private static final Log log = LogFactory.getLog(NioBlockingSelector.class);
+    
+    private static int threadCounter = 0;
+    
+    protected Selector sharedSelector;
+    
+    protected BlockPoller poller;
+    public NioBlockingSelector() {
+        
+    }
+    
+    public void open(Selector selector) {
+        sharedSelector = selector;
+        poller = new BlockPoller();
+        poller.selector = sharedSelector;
+        poller.setDaemon(true);
+        poller.setName("NioBlockingSelector.BlockPoller-"+(++threadCounter));
+        poller.start();
+    }
+    
+    public void close() {
+        if (poller!=null) {
+            poller.disable();
+            poller.interrupt();
+            poller = null;
+        }
+    }
+
+    /**
+     * Performs a blocking write using the bytebuffer for data to be written
+     * If the <code>selector</code> parameter is null, then it will perform a busy write that could
+     * take up a lot of CPU cycles.
+     * @param buf ByteBuffer - the buffer containing the data, we will write as long as <code>(buf.hasRemaining()==true)</code>
+     * @param socket SocketChannel - the socket to write data to
+     * @param writeTimeout long - the timeout for this write operation in milliseconds, -1 means no timeout
+     * @return int - returns the number of bytes written
+     * @throws EOFException if write returns -1
+     * @throws SocketTimeoutException if the write times out
+     * @throws IOException if an IO Exception occurs in the underlying socket logic
+     */
+    public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {
+        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+        if ( key == null ) throw new IOException("Key no longer registered");
+        KeyReference reference = new KeyReference();
+        KeyAttachment att = (KeyAttachment) key.attachment();
+        int written = 0;
+        boolean timedout = false;
+        int keycount = 1; //assume we can write
+        long time = System.currentTimeMillis(); //start the timeout timer
+        try {
+            while ( (!timedout) && buf.hasRemaining()) {
+                if (keycount > 0) { //only write if we were registered for a write
+                    int cnt = socket.write(buf); //write the data
+                    lastWrite.set(cnt);
+                    if (cnt == -1)
+                        throw new EOFException();
+                    written += cnt;
+                    if (cnt > 0) {
+                        time = System.currentTimeMillis(); //reset our timeout timer
+                        continue; //we successfully wrote, try again without a selector
+                    }
+                }
+                try {
+                    if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);
+                    poller.add(att,SelectionKey.OP_WRITE,reference);
+                    att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);
+                }catch (InterruptedException ignore) {
+                    Thread.interrupted();
+                }
+                if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {
+                    //we got interrupted, but we haven't received notification from the poller.
+                    keycount = 0;
+                }else {
+                    //latch countdown has happened
+                    keycount = 1;
+                    att.resetWriteLatch();
+                }
+
+                if (writeTimeout > 0 && (keycount == 0))
+                    timedout = (System.currentTimeMillis() - time) >= writeTimeout;
+            } //while
+            if (timedout) 
+                throw new SocketTimeoutException();
+        } finally {
+            poller.remove(att,SelectionKey.OP_WRITE);
+            if (timedout && reference.key!=null) {
+                poller.cancelKey(reference.key);
+            }
+            reference.key = null;
+        }
+        return written;
+    }
+
+    /**
+     * Performs a blocking read using the bytebuffer for data to be read
+     * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+     * take up a lot of CPU cycles.
+     * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+     * @param socket SocketChannel - the socket to write data to
+     * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+     * @return int - returns the number of bytes read
+     * @throws EOFException if read returns -1
+     * @throws SocketTimeoutException if the read times out
+     * @throws IOException if an IO Exception occurs in the underlying socket logic
+     */
+    public int read(ByteBuffer buf, NioChannel socket, long readTimeout) throws IOException {
+        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+        if ( key == null ) throw new IOException("Key no longer registered");
+        KeyReference reference = new KeyReference();
+        KeyAttachment att = (KeyAttachment) key.attachment();
+        int read = 0;
+        boolean timedout = false;
+        int keycount = 1; //assume we can write
+        long time = System.currentTimeMillis(); //start the timeout timer
+        try {
+            while ( (!timedout) && read == 0) {
+                if (keycount > 0) { //only read if we were registered for a read
+                    int cnt = socket.read(buf);
+                    if (cnt == -1)
+                        throw new EOFException();
+                    read += cnt;
+                    if (cnt > 0)
+                        break;
+                }
+                try {
+                    if ( att.getReadLatch()==null || att.getReadLatch().getCount()==0) att.startReadLatch(1);
+                    poller.add(att,SelectionKey.OP_READ, reference);
+                    att.awaitReadLatch(readTimeout,TimeUnit.MILLISECONDS);
+                }catch (InterruptedException ignore) {
+                    Thread.interrupted();
+                }
+                if ( att.getReadLatch()!=null && att.getReadLatch().getCount()> 0) {
+                    //we got interrupted, but we haven't received notification from the poller.
+                    keycount = 0;
+                }else {
+                    //latch countdown has happened
+                    keycount = 1;
+                    att.resetReadLatch();
+                }
+                if (readTimeout > 0 && (keycount == 0))
+                    timedout = (System.currentTimeMillis() - time) >= readTimeout;
+            } //while
+            if (timedout)
+                throw new SocketTimeoutException();
+        } finally {
+            poller.remove(att,SelectionKey.OP_READ);
+            if (timedout && reference.key!=null) {
+                poller.cancelKey(reference.key);
+            }
+            reference.key = null;
+        }
+        return read;
+    }
+
+    
+    protected static class BlockPoller extends Thread {
+        protected volatile boolean run = true;
+        protected Selector selector = null;
+        protected ConcurrentLinkedQueue<Runnable> events = new ConcurrentLinkedQueue<Runnable>();
+        public void disable() { run = false; selector.wakeup();}
+        protected AtomicInteger wakeupCounter = new AtomicInteger(0);
+        public void cancelKey(final SelectionKey key) {
+            Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    key.cancel();
+                }
+            };
+            events.offer(r);
+            wakeup();
+        }
+
+        public void wakeup() {
+            if (wakeupCounter.addAndGet(1)==0) selector.wakeup();
+        }
+
+        public void cancel(SelectionKey sk, KeyAttachment key, int ops){
+            if (sk!=null) {
+                sk.cancel();
+                sk.attach(null);
+                if (SelectionKey.OP_WRITE==(ops&SelectionKey.OP_WRITE)) countDown(key.getWriteLatch());
+                if (SelectionKey.OP_READ==(ops&SelectionKey.OP_READ))countDown(key.getReadLatch());
+            }
+        }
+        
+        public void add(final KeyAttachment key, final int ops, final KeyReference ref) {
+            Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    if ( key == null ) return;
+                    NioChannel nch = key.getChannel();
+                    if ( nch == null ) return;
+                    SocketChannel ch = nch.getIOChannel();
+                    if ( ch == null ) return;
+                    SelectionKey sk = ch.keyFor(selector);
+                    try {
+                        if (sk == null) {
+                            sk = ch.register(selector, ops, key);
+                            ref.key = sk;
+                        } else if (!sk.isValid()) {
+                            cancel(sk,key,ops);
+                        } else {
+                            sk.interestOps(sk.interestOps() | ops);
+                        }
+                    }catch (CancelledKeyException cx) {
+                        cancel(sk,key,ops);
+                    }catch (ClosedChannelException cx) {
+                        cancel(sk,key,ops);
+                    }
+                }
+            };
+            events.offer(r);
+            wakeup();
+        }
+        
+        public void remove(final KeyAttachment key, final int ops) {
+            Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    if ( key == null ) return;
+                    NioChannel nch = key.getChannel();
+                    if ( nch == null ) return;
+                    SocketChannel ch = nch.getIOChannel();
+                    if ( ch == null ) return;
+                    SelectionKey sk = ch.keyFor(selector);
+                    try {
+                        if (sk == null) {
+                            if (SelectionKey.OP_WRITE==(ops&SelectionKey.OP_WRITE)) countDown(key.getWriteLatch());
+                            if (SelectionKey.OP_READ==(ops&SelectionKey.OP_READ))countDown(key.getReadLatch());
+                        } else {
+                            if (sk.isValid()) {
+                                sk.interestOps(sk.interestOps() & (~ops));
+                                if (SelectionKey.OP_WRITE==(ops&SelectionKey.OP_WRITE)) countDown(key.getWriteLatch());
+                                if (SelectionKey.OP_READ==(ops&SelectionKey.OP_READ))countDown(key.getReadLatch());
+                                if (sk.interestOps()==0) {
+                                    sk.cancel();
+                                    sk.attach(null);
+                                }
+                            }else {
+                                sk.cancel();
+                                sk.attach(null);
+                            }
+                        }
+                    }catch (CancelledKeyException cx) {
+                        if (sk!=null) {
+                            sk.cancel();
+                            sk.attach(null);
+                        }
+                    }
+                }
+            };
+            events.offer(r);
+            wakeup();
+        }
+
+
+        public boolean events() {
+            boolean result = false;
+            Runnable r = null;
+            result = (events.size() > 0);
+            while ( (r = events.poll()) != null ) {
+                r.run();
+                result = true;
+            }
+            return result;
+        }
+
+        @Override
+        public void run() {
+            while (run) {
+                try {
+                    events();
+                    int keyCount = 0;
+                    try {
+                        int i = wakeupCounter.get();
+                        if (i>0) 
+                            keyCount = selector.selectNow();
+                        else {
+                            wakeupCounter.set(-1);
+                            keyCount = selector.select(1000);
+                        }
+                        wakeupCounter.set(0);
+                        if (!run) break;
+                    }catch ( NullPointerException x ) {
+                        //sun bug 5076772 on windows JDK 1.5
+                        if (selector==null) throw x;
+                        if ( log.isDebugEnabled() ) log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5",x);
+                        continue;
+                    } catch ( CancelledKeyException x ) {
+                        //sun bug 5076772 on windows JDK 1.5
+                        if ( log.isDebugEnabled() ) log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5",x);
+                        continue;
+                    } catch (Throwable x) {
+                        ExceptionUtils.handleThrowable(x);
+                        log.error("",x);
+                        continue;
+                    }
+
+                    Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
+
+                    // Walk through the collection of ready keys and dispatch
+                    // any active event.
+                    while (run && iterator != null && iterator.hasNext()) {
+                        SelectionKey sk = iterator.next();
+                        KeyAttachment attachment = (KeyAttachment)sk.attachment();
+                        try {
+                            attachment.access();
+                            iterator.remove();
+                            sk.interestOps(sk.interestOps() & (~sk.readyOps()));
+                            if ( sk.isReadable() ) {
+                                countDown(attachment.getReadLatch());
+                            }
+                            if (sk.isWritable()) {
+                                countDown(attachment.getWriteLatch());
+                            }
+                        }catch (CancelledKeyException ckx) {
+                            sk.cancel();
+                            countDown(attachment.getReadLatch());
+                            countDown(attachment.getWriteLatch());
+                        }
+                    }//while
+                }catch ( Throwable t ) {
+                    log.error("",t);
+                }
+            }
+            events.clear();
+            try {
+                selector.selectNow();//cancel all remaining keys
+            }catch( Exception ignore ) {
+                if (log.isDebugEnabled())log.debug("",ignore);
+            }
+            try {
+                selector.close();//Close the connector
+            }catch( Exception ignore ) {
+                if (log.isDebugEnabled())log.debug("",ignore);
+            }
+        }
+        
+        public void countDown(CountDownLatch latch) {
+            if ( latch == null ) return;
+            latch.countDown();
+        }
+    }
+    
+    public static class KeyReference {
+        SelectionKey key = null;
+        
+        @Override
+        public void finalize() {
+            if (key!=null && key.isValid()) {
+                log.warn("Possible key leak, cancelling key in the finalizer.");
+                try {key.cancel();}catch (Exception ignore){}
+            }
+            key = null;
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioChannel.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioChannel.java
new file mode 100644
index 0000000..9f70fe6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioChannel.java
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.tomcat.util.net;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+
+import org.apache.tomcat.util.MutableInteger;
+import org.apache.tomcat.util.net.NioEndpoint.Poller;
+import org.apache.tomcat.util.net.SecureNioChannel.ApplicationBufferHandler;
+
+/**
+ * 
+ * Base class for a SocketChannel wrapper used by the endpoint.
+ * This way, logic for a SSL socket channel remains the same as for
+ * a non SSL, making sure we don't need to code for any exception cases.
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class NioChannel implements ByteChannel{
+
+    protected static ByteBuffer emptyBuf = ByteBuffer.allocate(0);
+
+    protected SocketChannel sc = null;
+
+    protected ApplicationBufferHandler bufHandler;
+
+    protected Poller poller;
+    
+    protected boolean sendFile = false;
+
+    public NioChannel(SocketChannel channel, ApplicationBufferHandler bufHandler) throws IOException {
+        this.sc = channel;
+        this.bufHandler = bufHandler;
+    }
+
+    public void reset() throws IOException {
+        bufHandler.getReadBuffer().clear();
+        bufHandler.getWriteBuffer().clear();
+        this.sendFile = false;
+    }
+    
+    public int getBufferSize() {
+        if ( bufHandler == null ) return 0;
+        int size = 0;
+        size += bufHandler.getReadBuffer()!=null?bufHandler.getReadBuffer().capacity():0;
+        size += bufHandler.getWriteBuffer()!=null?bufHandler.getWriteBuffer().capacity():0;
+        return size;
+    }
+
+    /**
+     * returns true if the network buffer has 
+     * been flushed out and is empty
+     * @return boolean
+     */
+    public boolean flush(boolean block, Selector s, long timeout,MutableInteger lastWrite) throws IOException {
+        if (lastWrite!=null) lastWrite.set(1);
+        return true; //no network buffer in the regular channel
+    }
+
+
+    /**
+     * Closes this channel.
+     *
+     * @throws IOException If an I/O error occurs
+     * TODO Implement this java.nio.channels.Channel method
+     */
+    public void close() throws IOException {
+        getIOChannel().socket().close();
+        getIOChannel().close();
+    }
+
+    public void close(boolean force) throws IOException {
+        if (isOpen() || force ) close();
+    }
+    /**
+     * Tells whether or not this channel is open.
+     *
+     * @return <tt>true</tt> if, and only if, this channel is open
+     * TODO Implement this java.nio.channels.Channel method
+     */
+    public boolean isOpen() {
+        return sc.isOpen();
+    }
+
+    /**
+     * Writes a sequence of bytes to this channel from the given buffer.
+     *
+     * @param src The buffer from which bytes are to be retrieved
+     * @return The number of bytes written, possibly zero
+     * @throws IOException If some other I/O error occurs
+     * TODO Implement this java.nio.channels.WritableByteChannel method
+     */
+    public int write(ByteBuffer src) throws IOException {
+        return sc.write(src);
+    }
+
+    /**
+     * Reads a sequence of bytes from this channel into the given buffer.
+     *
+     * @param dst The buffer into which bytes are to be transferred
+     * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached end-of-stream
+     * @throws IOException If some other I/O error occurs
+     * TODO Implement this java.nio.channels.ReadableByteChannel method
+     */
+    public int read(ByteBuffer dst) throws IOException {
+        return sc.read(dst);
+    }
+
+    public Object getAttachment(boolean remove) {
+        Poller pol = getPoller();
+        Selector sel = pol!=null?pol.getSelector():null;
+        SelectionKey key = sel!=null?getIOChannel().keyFor(sel):null;
+        Object att = key!=null?key.attachment():null;
+        if (key != null && att != null && remove ) key.attach(null);
+        return att;
+    }
+    /**
+     * getBufHandler
+     *
+     * @return ApplicationBufferHandler
+     * TODO Implement this org.apache.tomcat.util.net.SecureNioChannel method
+     */
+    public ApplicationBufferHandler getBufHandler() {
+        return bufHandler;
+    }
+
+    public Poller getPoller() {
+        return poller;
+    }
+    /**
+     * getIOChannel
+     *
+     * @return SocketChannel
+     * TODO Implement this org.apache.tomcat.util.net.SecureNioChannel method
+     */
+    public SocketChannel getIOChannel() {
+        return sc;
+    }
+
+    /**
+     * isClosing
+     *
+     * @return boolean
+     * TODO Implement this org.apache.tomcat.util.net.SecureNioChannel method
+     */
+    public boolean isClosing() {
+        return false;
+    }
+
+    /**
+     * isInitHandshakeComplete
+     *
+     * @return boolean
+     */
+    public boolean isHandshakeComplete() {
+        return true;
+    }
+
+    public int handshake(boolean read, boolean write) throws IOException {
+        return 0;
+    }
+
+    public void setPoller(Poller poller) {
+        this.poller = poller;
+    }
+
+    public void setIOChannel(SocketChannel IOChannel) {
+        this.sc = IOChannel;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()+":"+this.sc.toString();
+    }
+    
+    public int getOutboundRemaining() {
+        return 0;
+    }
+    
+    /**
+     * Return true if the buffer wrote data
+     * @throws IOException
+     */
+    public boolean flushOutbound() throws IOException {
+        return false;
+    }
+    
+    public boolean isSendFile() {
+        return sendFile;
+    }
+    
+    public void setSendFile(boolean s) {
+        this.sendFile = s;
+    }
+    
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioEndpoint.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioEndpoint.java
new file mode 100644
index 0000000..0d751e7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioEndpoint.java
@@ -0,0 +1,1620 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.X509KeyManager;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.SecureNioChannel.ApplicationBufferHandler;
+import org.apache.tomcat.util.net.jsse.NioX509KeyManager;
+
+/**
+ * NIO tailored thread pool, providing the following services:
+ * <ul>
+ * <li>Socket acceptor thread</li>
+ * <li>Socket poller thread</li>
+ * <li>Worker threads pool</li>
+ * </ul>
+ *
+ * When switching to Java 5, there's an opportunity to use the virtual
+ * machine's thread pool.
+ *
+ * @author Mladen Turk
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ */
+public class NioEndpoint extends AbstractEndpoint {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    private static final Log log = LogFactory.getLog(NioEndpoint.class);
+
+
+    public static final int OP_REGISTER = 0x100; //register interest op
+    public static final int OP_CALLBACK = 0x200; //callback interest op
+    
+    // ----------------------------------------------------------------- Fields
+
+    protected NioSelectorPool selectorPool = new NioSelectorPool();
+    
+    /**
+     * Server socket "pointer".
+     */
+    protected ServerSocketChannel serverSock = null;
+    
+    /**
+     * use send file
+     */
+    protected boolean useSendfile = true;
+    
+    /**
+     * The size of the OOM parachute.
+     */
+    protected int oomParachute = 1024*1024;
+    /**
+     * The oom parachute, when an OOM error happens, 
+     * will release the data, giving the JVM instantly 
+     * a chunk of data to be able to recover with.
+     */
+    protected byte[] oomParachuteData = null;
+    
+    /**
+     * Make sure this string has already been allocated
+     */
+    protected static final String oomParachuteMsg = 
+        "SEVERE:Memory usage is low, parachute is non existent, your system may start failing.";
+    
+    /**
+     * Keep track of OOM warning messages.
+     */
+    long lastParachuteCheck = System.currentTimeMillis();
+    
+    /**
+     * 
+     */
+    protected volatile CountDownLatch stopLatch = null;
+    
+    /**
+     * Cache for SocketProcessor objects
+     */
+    protected ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>() {
+        private static final long serialVersionUID = 1L;
+        protected AtomicInteger size = new AtomicInteger(0);
+        @Override
+        public boolean offer(SocketProcessor sc) {
+            sc.reset(null,null);
+            boolean offer = socketProperties.getProcessorCache()==-1?true:size.get()<socketProperties.getProcessorCache();
+            //avoid over growing our cache or add after we have stopped
+            if ( running && (!paused) && (offer) ) {
+                boolean result = super.offer(sc);
+                if ( result ) {
+                    size.incrementAndGet();
+                }
+                return result;
+            }
+            else return false;
+        }
+        
+        @Override
+        public SocketProcessor poll() {
+            SocketProcessor result = super.poll();
+            if ( result != null ) {
+                size.decrementAndGet();
+            }
+            return result;
+        }
+        
+        @Override
+        public void clear() {
+            super.clear();
+            size.set(0);
+        }
+    };
+
+
+    /**
+     * Cache for key attachment objects
+     */
+    protected ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>() {
+        private static final long serialVersionUID = 1L;
+        protected AtomicInteger size = new AtomicInteger(0);
+        @Override
+        public boolean offer(KeyAttachment ka) {
+            ka.reset();
+            boolean offer = socketProperties.getKeyCache()==-1?true:size.get()<socketProperties.getKeyCache();
+            //avoid over growing our cache or add after we have stopped
+            if ( running && (!paused) && (offer) ) {
+                boolean result = super.offer(ka);
+                if ( result ) {
+                    size.incrementAndGet();
+                }
+                return result;
+            }
+            else return false;
+        }
+
+        @Override
+        public KeyAttachment poll() {
+            KeyAttachment result = super.poll();
+            if ( result != null ) {
+                size.decrementAndGet();
+            }
+            return result;
+        }
+
+        @Override
+        public void clear() {
+            super.clear();
+            size.set(0);
+        }
+    };
+
+    
+    /**
+     * Cache for poller events
+     */
+    protected ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>() {
+        private static final long serialVersionUID = 1L;
+        protected AtomicInteger size = new AtomicInteger(0);
+        @Override
+        public boolean offer(PollerEvent pe) {
+            pe.reset();
+            boolean offer = socketProperties.getEventCache()==-1?true:size.get()<socketProperties.getEventCache();
+            //avoid over growing our cache or add after we have stopped
+            if ( running && (!paused) && (offer) ) {
+                boolean result = super.offer(pe);
+                if ( result ) {
+                    size.incrementAndGet();
+                }
+                return result;
+            }
+            else return false;
+        }
+
+        @Override
+        public PollerEvent poll() {
+            PollerEvent result = super.poll();
+            if ( result != null ) {
+                size.decrementAndGet();
+            }
+            return result;
+        }
+
+        @Override
+        public void clear() {
+            super.clear();
+            size.set(0);
+        }
+    };
+
+
+    /**
+     * Bytebuffer cache, each channel holds a set of buffers (two, except for SSL holds four)
+     */
+    protected ConcurrentLinkedQueue<NioChannel> nioChannels = new ConcurrentLinkedQueue<NioChannel>() {
+        private static final long serialVersionUID = 1L;
+        protected AtomicInteger size = new AtomicInteger(0);
+        protected AtomicInteger bytes = new AtomicInteger(0);
+        @Override
+        public boolean offer(NioChannel socket) {
+            boolean offer = socketProperties.getBufferPool()==-1?true:size.get()<socketProperties.getBufferPool();
+            offer = offer && (socketProperties.getBufferPoolSize()==-1?true:(bytes.get()+socket.getBufferSize())<socketProperties.getBufferPoolSize());
+            //avoid over growing our cache or add after we have stopped
+            if ( running && (!paused) && (offer) ) {
+                boolean result = super.offer(socket);
+                if ( result ) {
+                    size.incrementAndGet();
+                    bytes.addAndGet(socket.getBufferSize());
+                }
+                return result;
+            }
+            else return false;
+        }
+        
+        @Override
+        public NioChannel poll() {
+            NioChannel result = super.poll();
+            if ( result != null ) {
+                size.decrementAndGet();
+                bytes.addAndGet(-result.getBufferSize());
+            }
+            return result;
+        }
+        
+        @Override
+        public void clear() {
+            super.clear();
+            size.set(0);
+            bytes.set(0);
+        }
+    };
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Generic properties, introspected
+     */
+    @Override
+    public boolean setProperty(String name, String value) {
+        final String selectorPoolName = "selectorPool.";
+        try {
+            if (name.startsWith(selectorPoolName)) {
+                return IntrospectionUtils.setProperty(selectorPool, name.substring(selectorPoolName.length()), value);
+            } else { 
+                return super.setProperty(name, value);
+            }
+        }catch ( Exception x ) {
+            log.error("Unable to set attribute \""+name+"\" to \""+value+"\"",x);
+            return false;
+        }
+    }
+
+
+    /**
+     * Priority of the acceptor threads.
+     */
+    protected int acceptorThreadPriority = Thread.NORM_PRIORITY;
+    public void setAcceptorThreadPriority(int acceptorThreadPriority) { this.acceptorThreadPriority = acceptorThreadPriority; }
+    public int getAcceptorThreadPriority() { return acceptorThreadPriority; }
+
+    /**
+     * Priority of the poller threads.
+     */
+    protected int pollerThreadPriority = Thread.NORM_PRIORITY;
+    public void setPollerThreadPriority(int pollerThreadPriority) { this.pollerThreadPriority = pollerThreadPriority; }
+    public int getPollerThreadPriority() { return pollerThreadPriority; }
+
+
+    /**
+     * Handling of accepted sockets.
+     */
+    protected Handler handler = null;
+    public void setHandler(Handler handler ) { this.handler = handler; }
+    public Handler getHandler() { return handler; }
+
+
+    /**
+     * Allow comet request handling.
+     */
+    protected boolean useComet = true;
+    public void setUseComet(boolean useComet) { this.useComet = useComet; }
+    public boolean getUseComet() { return useComet; }
+
+
+    /**
+     * Acceptor thread count.
+     */
+    protected int acceptorThreadCount = 1;
+    public void setAcceptorThreadCount(int acceptorThreadCount) { this.acceptorThreadCount = acceptorThreadCount; }
+    public int getAcceptorThreadCount() { return acceptorThreadCount; }
+
+
+    /**
+     * Poller thread count.
+     */
+    protected int pollerThreadCount = Runtime.getRuntime().availableProcessors();
+    public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
+    public int getPollerThreadCount() { return pollerThreadCount; }
+
+    protected long selectorTimeout = 1000;
+    public void setSelectorTimeout(long timeout){ this.selectorTimeout = timeout;}
+    public long getSelectorTimeout(){ return this.selectorTimeout; }
+    /**
+     * The socket poller.
+     */
+    protected Poller[] pollers = null;
+    protected AtomicInteger pollerRotater = new AtomicInteger(0);
+    /**
+     * Return an available poller in true round robin fashion
+     */
+    public Poller getPoller0() {
+        int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
+        return pollers[idx];
+    }
+
+
+    public void setSelectorPool(NioSelectorPool selectorPool) {
+        this.selectorPool = selectorPool;
+    }
+
+    public void setSocketProperties(SocketProperties socketProperties) {
+        this.socketProperties = socketProperties;
+    }
+
+    public void setUseSendfile(boolean useSendfile) {
+        this.useSendfile = useSendfile;
+    }
+
+    /**
+     * Is deferAccept supported?
+     */
+    @Override
+    public boolean getDeferAccept() {
+        // Not supported
+        return false;
+    }
+
+    public void setOomParachute(int oomParachute) {
+        this.oomParachute = oomParachute;
+    }
+
+    public void setOomParachuteData(byte[] oomParachuteData) {
+        this.oomParachuteData = oomParachuteData;
+    }
+
+
+    protected SSLContext sslContext = null;
+    public SSLContext getSSLContext() { return sslContext;}
+    public void setSSLContext(SSLContext c) { sslContext = c;}
+    
+    // --------------------------------------------------------- OOM Parachute Methods
+
+    protected void checkParachute() {
+        boolean para = reclaimParachute(false);
+        if (!para && (System.currentTimeMillis()-lastParachuteCheck)>10000) {
+            try {
+                log.fatal(oomParachuteMsg);
+            }catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                System.err.println(oomParachuteMsg);
+            }
+            lastParachuteCheck = System.currentTimeMillis();
+        }
+    }
+    
+    protected boolean reclaimParachute(boolean force) {
+        if ( oomParachuteData != null ) return true;
+        if ( oomParachute > 0 && ( force || (Runtime.getRuntime().freeMemory() > (oomParachute*2))) )  
+            oomParachuteData = new byte[oomParachute];
+        return oomParachuteData != null;
+    }
+    
+    protected void releaseCaches() {
+        this.keyCache.clear();
+        this.nioChannels.clear();
+        this.processorCache.clear();
+        if ( handler != null ) handler.recycle();
+        
+    }
+    
+    // --------------------------------------------------------- Public Methods
+    /**
+     * Number of keepalive sockets.
+     */
+    public int getKeepAliveCount() {
+        if (pollers == null) {
+            return 0;
+        } else {
+            int sum = 0;
+            for (int i=0; i<pollers.length; i++) {
+                sum += pollers[i].getKeyCount();
+            }
+            return sum;
+        }
+    }
+
+
+    // ----------------------------------------------- Public Lifecycle Methods
+
+
+    /**
+     * Initialize the endpoint.
+     */
+    @Override
+    public void bind() throws Exception {
+
+        serverSock = ServerSocketChannel.open();
+        socketProperties.setProperties(serverSock.socket());
+        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
+        serverSock.socket().bind(addr,getBacklog()); 
+        serverSock.configureBlocking(true); //mimic APR behavior
+        serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());
+
+        // Initialize thread count defaults for acceptor, poller
+        if (acceptorThreadCount == 0) {
+            // FIXME: Doesn't seem to work that well with multiple accept threads
+            acceptorThreadCount = 1;
+        }
+        if (pollerThreadCount <= 0) {
+            //minimum one poller thread
+            pollerThreadCount = 1;
+        }
+        stopLatch = new CountDownLatch(pollerThreadCount);
+
+        // Initialize SSL if needed
+        if (isSSLEnabled()) {
+            SSLUtil sslUtil = handler.getSslImplementation().getSSLUtil(this);
+
+            sslContext = sslUtil.createSSLContext();
+            sslContext.init(wrap(sslUtil.getKeyManagers()),
+                    sslUtil.getTrustManagers(), null);
+
+            SSLSessionContext sessionContext =
+                sslContext.getServerSessionContext();
+            if (sessionContext != null) {
+                sslUtil.configureSessionContext(sessionContext);
+            }
+        }
+        
+        if (oomParachute>0) reclaimParachute(true);
+        selectorPool.open();
+    }
+    
+    public KeyManager[] wrap(KeyManager[] managers) {
+        if (managers==null) return null;
+        KeyManager[] result = new KeyManager[managers.length];
+        for (int i=0; i<result.length; i++) {
+            if (managers[i] instanceof X509KeyManager && getKeyAlias()!=null) {
+                result[i] = new NioX509KeyManager((X509KeyManager)managers[i],getKeyAlias());
+            } else {
+                result[i] = managers[i];
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Start the NIO endpoint, creating acceptor, poller threads.
+     */
+    @Override
+    public void startInternal() throws Exception {
+
+        if (!running) {
+            running = true;
+            paused = false;
+             
+            // Create worker collection
+            if ( getExecutor() == null ) {
+                createExecutor();
+            }
+
+            initializeConnectionLatch();
+            
+            // Start poller threads
+            pollers = new Poller[getPollerThreadCount()];
+            for (int i=0; i<pollers.length; i++) {
+                pollers[i] = new Poller();
+                Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
+                pollerThread.setPriority(threadPriority);
+                pollerThread.setDaemon(true);
+                pollerThread.start();
+            }
+
+            // Start acceptor threads
+            for (int i = 0; i < acceptorThreadCount; i++) {
+                Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
+                acceptorThread.setPriority(threadPriority);
+                acceptorThread.setDaemon(getDaemon());
+                acceptorThread.start();
+            }
+        }
+    }
+
+
+    /**
+     * Stop the endpoint. This will cause all processing threads to stop.
+     */
+    @Override
+    public void stopInternal() {
+        releaseConnectionLatch();
+        if (!paused) {
+            pause();
+        }
+        if (running) {
+            running = false;
+            unlockAccept();
+            for (int i=0; pollers!=null && i<pollers.length; i++) {
+                if (pollers[i]==null) continue;
+                pollers[i].destroy();
+                pollers[i] = null;
+            }
+            try { stopLatch.await(selectorTimeout+100,TimeUnit.MILLISECONDS); } catch (InterruptedException ignore ) {}
+        }
+        eventCache.clear();
+        keyCache.clear();
+        nioChannels.clear();
+        processorCache.clear();
+        shutdownExecutor();
+        
+    }
+
+
+    /**
+     * Deallocate NIO memory pools, and close server socket.
+     */
+    @Override
+    public void unbind() throws Exception {
+        if (log.isDebugEnabled()) {
+            log.debug("Destroy initiated for "+new InetSocketAddress(getAddress(),getPort()));
+        }
+        if (running) {
+            stop();
+        }
+        // Close server socket
+        serverSock.socket().close();
+        serverSock.close();
+        serverSock = null;
+        sslContext = null;
+        releaseCaches();
+        selectorPool.close();
+        if (log.isDebugEnabled()) {
+            log.debug("Destroy completed for "+new InetSocketAddress(getAddress(),getPort()));
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    public int getWriteBufSize() {
+        return socketProperties.getTxBufSize();
+    }
+
+    public int getReadBufSize() {
+        return socketProperties.getRxBufSize();
+    }
+
+    public NioSelectorPool getSelectorPool() {
+        return selectorPool;
+    }
+
+    @Override
+    public boolean getUseSendfile() {
+        return useSendfile;
+    }
+
+    public int getOomParachute() {
+        return oomParachute;
+    }
+
+    public byte[] getOomParachuteData() {
+        return oomParachuteData;
+    }
+
+
+    /**
+     * Process the specified connection.
+     */
+    protected boolean setSocketOptions(SocketChannel socket) {
+        // Process the connection
+        try {
+            //disable blocking, APR style, we are gonna be polling it
+            socket.configureBlocking(false);
+            Socket sock = socket.socket();
+            socketProperties.setProperties(sock);
+
+            NioChannel channel = nioChannels.poll();
+            if ( channel == null ) {
+                // SSL setup
+                if (sslContext != null) {
+                    SSLEngine engine = createSSLEngine();
+                    int appbufsize = engine.getSession().getApplicationBufferSize();
+                    NioBufferHandler bufhandler = new NioBufferHandler(Math.max(appbufsize,socketProperties.getAppReadBufSize()),
+                                                                       Math.max(appbufsize,socketProperties.getAppWriteBufSize()),
+                                                                       socketProperties.getDirectBuffer());
+                    channel = new SecureNioChannel(socket, engine, bufhandler, selectorPool);
+                } else {
+                    // normal tcp setup
+                    NioBufferHandler bufhandler = new NioBufferHandler(socketProperties.getAppReadBufSize(),
+                                                                       socketProperties.getAppWriteBufSize(),
+                                                                       socketProperties.getDirectBuffer());
+
+                    channel = new NioChannel(socket, bufhandler);
+                }
+            } else {                
+                channel.setIOChannel(socket);
+                if ( channel instanceof SecureNioChannel ) {
+                    SSLEngine engine = createSSLEngine();
+                    ((SecureNioChannel)channel).reset(engine);
+                } else {
+                    channel.reset();
+                }
+            }
+            getPoller0().register(channel);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            try {
+                log.error("",t);
+            } catch (Throwable tt) {
+                ExceptionUtils.handleThrowable(t);
+            }
+            // Tell to close the socket
+            return false;
+        }
+        return true;
+    }
+
+    protected SSLEngine createSSLEngine() {
+        SSLEngine engine = sslContext.createSSLEngine();
+        if ("false".equals(getClientAuth())) {
+            engine.setNeedClientAuth(false);
+            engine.setWantClientAuth(false);
+        } else if ("true".equals(getClientAuth()) || "yes".equals(getClientAuth())){
+            engine.setNeedClientAuth(true);
+        } else if ("want".equals(getClientAuth())) {
+            engine.setWantClientAuth(true);
+        }
+        engine.setUseClientMode(false);
+        if ( getCiphersArray().length > 0 ) engine.setEnabledCipherSuites(getCiphersArray());
+        if ( getSslEnabledProtocolsArray().length > 0 ) engine.setEnabledProtocols(getSslEnabledProtocolsArray());
+        
+        return engine;
+    }
+
+
+    /**
+     * Returns true if a worker thread is available for processing.
+     * @return boolean
+     */
+    protected boolean isWorkerAvailable() {
+        return true;
+    }
+
+    public boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
+        try {
+            KeyAttachment attachment = (KeyAttachment)socket.getAttachment(false);
+            attachment.setCometNotify(false); //will get reset upon next reg
+            SocketProcessor sc = processorCache.poll();
+            if ( sc == null ) sc = new SocketProcessor(socket,status);
+            else sc.reset(socket,status);
+            if ( dispatch && getExecutor()!=null ) getExecutor().execute(sc);
+            else sc.run();
+        } catch (RejectedExecutionException rx) {
+            log.warn("Socket processing request was rejected for:"+socket,rx);
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This means we got an OOM or similar creating a thread, or that
+            // the pool and its queue are full
+            log.error(sm.getString("endpoint.process.fail"), t);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+    // --------------------------------------------------- Acceptor Inner Class
+
+
+    /**
+     * Server socket acceptor thread.
+     */
+    protected class Acceptor implements Runnable {
+        /**
+         * The background thread that listens for incoming TCP/IP connections and
+         * hands them off to an appropriate processor.
+         */
+        @Override
+        public void run() {
+
+            int errorDelay = 0;
+
+            // Loop until we receive a shutdown command
+            while (running) {
+                
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                try {
+                    //if we have reached max connections, wait
+                    awaitConnection();
+                    
+                    SocketChannel socket = null;
+                    try {
+                        // Accept the next incoming connection from the server
+                        // socket
+                        socket = serverSock.accept();
+                    } catch (IOException ioe) {
+                        // Introduce delay if necessary
+                        errorDelay = handleExceptionWithDelay(errorDelay);
+                        // re-throw
+                        throw ioe;
+                    }
+                    // Successful accept, reset the error delay
+                    errorDelay = 0;
+
+                    // Hand this socket off to an appropriate processor
+                    //TODO FIXME - this is currently a blocking call, meaning we will be blocking
+                    //further accepts until there is a thread available.
+                    if ( running && (!paused) && socket != null ) {
+                        // setSocketOptions() will add channel to the poller
+                        // if successful
+                        if (!setSocketOptions(socket)) {
+                            try {
+                                socket.socket().close();
+                                socket.close();
+                            } catch (IOException ix) {
+                                if (log.isDebugEnabled())
+                                    log.debug("", ix);
+                            }
+                        } else {
+                            countUpConnection();
+                        }
+                    }
+                } catch (SocketTimeoutException sx) {
+                    //normal condition
+                } catch (IOException x) {
+                    if (running) {
+                        log.error(sm.getString("endpoint.accept.fail"), x);
+                    }
+                } catch (OutOfMemoryError oom) {
+                    try {
+                        oomParachuteData = null;
+                        releaseCaches();
+                        log.error("", oom);
+                    }catch ( Throwable oomt ) {
+                        try {
+                            try {
+                                System.err.println(oomParachuteMsg);
+                                oomt.printStackTrace();
+                            }catch (Throwable letsHopeWeDontGetHere){
+                                ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
+                            }
+                        }catch (Throwable letsHopeWeDontGetHere){
+                            ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
+                        }
+                    }
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("endpoint.accept.fail"), t);
+                }
+            }//while
+        }//run
+    }
+
+
+    // ----------------------------------------------------- Poller Inner Classes
+
+    /**
+     * 
+     * PollerEvent, cacheable object for poller events to avoid GC
+     */
+    public static class PollerEvent implements Runnable {
+        
+        protected NioChannel socket;
+        protected int interestOps;
+        protected KeyAttachment key;
+        public PollerEvent(NioChannel ch, KeyAttachment k, int intOps) {
+            reset(ch, k, intOps);
+        }
+    
+        public void reset(NioChannel ch, KeyAttachment k, int intOps) {
+            socket = ch;
+            interestOps = intOps;
+            key = k;
+        }
+    
+        public void reset() {
+            reset(null, null, 0);
+        }
+    
+        @Override
+        public void run() {
+            if ( interestOps == OP_REGISTER ) {
+                try {
+                    socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
+                } catch (Exception x) {
+                    log.error("", x);
+                }
+            } else {
+                final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+                try {
+                    boolean cancel = false;
+                    if (key != null) {
+                        final KeyAttachment att = (KeyAttachment) key.attachment();
+                        if ( att!=null ) {
+                            //handle callback flag
+                            if (att.getComet() && (interestOps & OP_CALLBACK) == OP_CALLBACK ) {
+                                att.setCometNotify(true);
+                            } else {
+                                att.setCometNotify(false);
+                            }
+                            interestOps = (interestOps & (~OP_CALLBACK));//remove the callback flag
+                            att.access();//to prevent timeout
+                            //we are registering the key to start with, reset the fairness counter.
+                            int ops = key.interestOps() | interestOps;
+                            att.interestOps(ops);
+                            key.interestOps(ops);
+                        } else {
+                            cancel = true;
+                        }
+                    } else {
+                        cancel = true;
+                    }
+                    if ( cancel ) socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
+                }catch (CancelledKeyException ckx) {
+                    try {
+                        socket.getPoller().cancelledKey(key,SocketStatus.DISCONNECT,true);
+                    }catch (Exception ignore) {}
+                }
+            }//end if
+        }//run
+        
+        @Override
+        public String toString() {
+            return super.toString()+"[intOps="+this.interestOps+"]";
+        }
+    }
+    
+    /**
+     * Poller class.
+     */
+    public class Poller implements Runnable {
+
+        protected Selector selector;
+        protected ConcurrentLinkedQueue<Runnable> events = new ConcurrentLinkedQueue<Runnable>();
+        
+        protected volatile boolean close = false;
+        protected long nextExpiration = 0;//optimize expiration handling
+        
+        protected AtomicLong wakeupCounter = new AtomicLong(0l);
+        
+        protected volatile int keyCount = 0;
+
+        public Poller() throws IOException {
+            this.selector = Selector.open();
+        }
+        
+        public int getKeyCount() { return keyCount; }
+        
+        public Selector getSelector() { return selector;}
+
+        /**
+         * Destroy the poller.
+         */
+        protected void destroy() {
+            // Wait for polltime before doing anything, so that the poller threads
+            // exit, otherwise parallel closure of sockets which are still
+            // in the poller can cause problems
+            close = true;
+            events.clear();
+            selector.wakeup();
+        }
+        
+        public void addEvent(Runnable event) {
+            events.offer(event);
+            if ( wakeupCounter.incrementAndGet() == 0 ) selector.wakeup();
+        }
+        
+        public void cometInterest(NioChannel socket) {
+            KeyAttachment att = (KeyAttachment)socket.getAttachment(false);
+            add(socket,att.getCometOps());
+            if ( (att.getCometOps()&OP_CALLBACK) == OP_CALLBACK ) {
+                nextExpiration = 0; //force the check for faster callback
+                selector.wakeup();
+            }
+        }
+        
+        /**
+         * Add specified socket and associated pool to the poller. The socket will
+         * be added to a temporary array, and polled first after a maximum amount
+         * of time equal to pollTime (in most cases, latency will be much lower,
+         * however).
+         *
+         * @param socket to add to the poller
+         */
+        public void add(final NioChannel socket) {
+            add(socket,SelectionKey.OP_READ);
+        }
+        
+        public void add(final NioChannel socket, final int interestOps) {
+            PollerEvent r = eventCache.poll();
+            if ( r==null) r = new PollerEvent(socket,null,interestOps);
+            else r.reset(socket,null,interestOps);
+            addEvent(r);
+        }
+        
+        public boolean events() {
+            boolean result = false;
+
+            Runnable r = null;
+            result = (events.size() > 0);
+            while ( (r = events.poll()) != null ) {
+                try {
+                    r.run();
+                    if ( r instanceof PollerEvent ) {
+                        ((PollerEvent)r).reset();
+                        eventCache.offer((PollerEvent)r);
+                    }
+                } catch ( Throwable x ) {
+                    log.error("",x);
+                }
+            }
+
+            return result;
+        }
+        
+        public void register(final NioChannel socket)
+        {
+            socket.setPoller(this);
+            KeyAttachment key = keyCache.poll();
+            final KeyAttachment ka = key!=null?key:new KeyAttachment(socket);
+            ka.reset(this,socket,getSocketProperties().getSoTimeout());
+            ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
+            PollerEvent r = eventCache.poll();
+            ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
+            if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
+            else r.reset(socket,ka,OP_REGISTER);
+            addEvent(r);
+        }
+        public void cancelledKey(SelectionKey key, SocketStatus status, boolean dispatch) {
+            try {
+                if ( key == null ) return;//nothing to do
+                KeyAttachment ka = (KeyAttachment) key.attachment();
+                if (ka != null && ka.getComet() && status != null) {
+                    //the comet event takes care of clean up
+                    //processSocket(ka.getChannel(), status, dispatch);
+                    ka.setComet(false);//to avoid a loop
+                    if (status == SocketStatus.TIMEOUT ) {
+                        if (processSocket(ka.getChannel(), status, true)) {
+                            return; // don't close on comet timeout
+                        }
+                    } else {
+                        processSocket(ka.getChannel(), status, false); //don't dispatch if the lines below are cancelling the key
+                    }                    
+                }
+                key.attach(null);
+                if (ka!=null) handler.release(ka.getChannel());
+                else handler.release((SocketChannel)key.channel());
+                if (key.isValid()) key.cancel();
+                if (key.channel().isOpen()) {
+                    try {
+                        key.channel().close();
+                    } catch (Exception e) {
+                        if (log.isDebugEnabled()) {
+                            log.debug(sm.getString(
+                                    "endpoint.debug.channelCloseFail"), e);
+                        }
+                    }
+                }
+                try {
+                    if (ka!=null) {
+                        ka.getSocket().close(true);
+                    }
+                } catch (Exception e){
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString(
+                                "endpoint.debug.socketCloseFail"), e);
+                    }
+                }
+                try {if (ka!=null && ka.getSendfileData()!=null && ka.getSendfileData().fchannel!=null && ka.getSendfileData().fchannel.isOpen()) ka.getSendfileData().fchannel.close();}catch (Exception ignore){}
+                if (ka!=null) {
+                    ka.reset();
+                    countDownConnection(); 
+                }
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                if (log.isDebugEnabled()) log.error("",e);
+            }
+        }
+        /**
+         * The background thread that listens for incoming TCP/IP connections and
+         * hands them off to an appropriate processor.
+         */
+        @Override
+        public void run() {
+            // Loop until we receive a shutdown command
+            while (running) {
+                try {
+                    // Loop if endpoint is paused
+                    while (paused && (!close) ) {
+                        try {
+                            Thread.sleep(100);
+                        } catch (InterruptedException e) {
+                            // Ignore
+                        }
+                    }
+
+                    boolean hasEvents = events();
+
+                    // Time to terminate?
+                    if (close) {
+                        timeout(0, false);
+                        break;
+                    }
+                    try {
+                        if ( !close ) {
+                            if (wakeupCounter.get()>0) {
+                                //if we are here, means we have other stuff to do
+                                //do a non blocking select
+                                keyCount = selector.selectNow();
+                            }else {
+                                keyCount = selector.keys().size();
+                                wakeupCounter.set(-1);
+                                keyCount = selector.select(selectorTimeout);
+                            }
+                            wakeupCounter.set(0);
+                        }
+                        if (close) {
+                            timeout(0, false);
+                            selector.close(); 
+                            break; 
+                        }
+                    } catch ( NullPointerException x ) {
+                        //sun bug 5076772 on windows JDK 1.5
+                        if ( log.isDebugEnabled() ) log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5",x);
+                        if ( wakeupCounter == null || selector == null ) throw x;
+                        continue;
+                    } catch ( CancelledKeyException x ) {
+                        //sun bug 5076772 on windows JDK 1.5
+                        if ( log.isDebugEnabled() ) log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5",x);
+                        if ( wakeupCounter == null || selector == null ) throw x;
+                        continue;
+                    } catch (Throwable x) {
+                        ExceptionUtils.handleThrowable(x);
+                        log.error("",x);
+                        continue;
+                    }
+                    //either we timed out or we woke up, process events first
+                    if ( keyCount == 0 ) hasEvents = (hasEvents | events());
+
+                    Iterator<SelectionKey> iterator =
+                        keyCount > 0 ? selector.selectedKeys().iterator() : null;
+                    // Walk through the collection of ready keys and dispatch
+                    // any active event.
+                    while (iterator != null && iterator.hasNext()) {
+                        SelectionKey sk = iterator.next();
+                        KeyAttachment attachment = (KeyAttachment)sk.attachment();
+                        // Attachment may be null if another thread has called
+                        // cancelledKey()
+                        if (attachment == null) {
+                            iterator.remove();
+                        } else {
+                            attachment.access();
+                            iterator.remove();
+                            processKey(sk, attachment);
+                        }
+                    }//while
+
+                    //process timeouts
+                    timeout(keyCount,hasEvents);
+                    if ( oomParachute > 0 && oomParachuteData == null ) checkParachute();
+                } catch (OutOfMemoryError oom) {
+                    try {
+                        oomParachuteData = null;
+                        releaseCaches();
+                        log.error("", oom);
+                    }catch ( Throwable oomt ) {
+                        try {
+                            System.err.println(oomParachuteMsg);
+                            oomt.printStackTrace();
+                        }catch (Throwable letsHopeWeDontGetHere){
+                            ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
+                        }
+                    }
+                }
+            }//while
+            synchronized (this) {
+                this.notifyAll();
+            }
+            stopLatch.countDown();
+
+        }
+        
+        protected boolean processKey(SelectionKey sk, KeyAttachment attachment) {
+            boolean result = true;
+            try {
+                if ( close ) {
+                    cancelledKey(sk, SocketStatus.STOP, false);
+                } else if ( sk.isValid() && attachment != null ) {
+                    attachment.access();//make sure we don't time out valid sockets
+                    sk.attach(attachment);//cant remember why this is here
+                    NioChannel channel = attachment.getChannel();
+                    if (sk.isReadable() || sk.isWritable() ) {
+                        if ( attachment.getSendfileData() != null ) {
+                            processSendfile(sk,attachment,true, false);
+                        } else if ( attachment.getComet() ) {
+                            //check if thread is available
+                            if ( isWorkerAvailable() ) {
+                                //set interest ops to 0 so we don't get multiple
+                                //Invocations for both read and write on separate threads
+                                reg(sk, attachment, 0);
+                                //read goes before write
+                                if (sk.isReadable()) {
+                                    //read notification
+                                    if (!processSocket(channel, SocketStatus.OPEN, true))
+                                        processSocket(channel, SocketStatus.DISCONNECT, true);
+                                } else {
+                                    //future placement of a WRITE notif
+                                    if (!processSocket(channel, SocketStatus.OPEN, true))
+                                        processSocket(channel, SocketStatus.DISCONNECT, true);
+                                }
+                            } else {
+                                result = false;
+                            }
+                        } else {
+                            //later on, improve latch behavior
+                            if ( isWorkerAvailable() ) {
+                                unreg(sk, attachment,sk.readyOps());
+                                boolean close = (!processSocket(channel, null, true));
+                                if (close) {
+                                    cancelledKey(sk,SocketStatus.DISCONNECT,false);
+                                }
+                            } else {
+                                result = false;
+                            }
+                        }
+                    } 
+                } else {
+                    //invalid key
+                    cancelledKey(sk, SocketStatus.ERROR,false);
+                }
+            } catch ( CancelledKeyException ckx ) {
+                cancelledKey(sk, SocketStatus.ERROR,false);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error("",t);
+            }
+            return result;
+        }
+        
+        public boolean processSendfile(SelectionKey sk, KeyAttachment attachment, boolean reg, boolean event) {
+            NioChannel sc = null;
+            try {
+                //unreg(sk,attachment);//only do this if we do process send file on a separate thread
+                SendfileData sd = attachment.getSendfileData();
+                if ( sd.fchannel == null ) {
+                    File f = new File(sd.fileName);
+                    if ( !f.exists() ) {
+                        cancelledKey(sk,SocketStatus.ERROR,false);
+                        return false;
+                    }
+                    sd.fchannel = new FileInputStream(f).getChannel();
+                }
+                sc = attachment.getChannel();
+                sc.setSendFile(true);
+                WritableByteChannel wc = ((sc instanceof SecureNioChannel)?sc:sc.getIOChannel());
+                
+                if (sc.getOutboundRemaining()>0) {
+                    if (sc.flushOutbound()) {
+                        attachment.access();
+                    }
+                } else {
+                    long written = sd.fchannel.transferTo(sd.pos,sd.length,wc);
+                    if ( written > 0 ) {
+                        sd.pos += written;
+                        sd.length -= written;
+                        attachment.access();
+                    }
+                }
+                if ( sd.length <= 0 && sc.getOutboundRemaining()<=0) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Send file complete for:"+sd.fileName);
+                    }
+                    attachment.setSendfileData(null);
+                    try {sd.fchannel.close();}catch(Exception ignore){}
+                    if ( sd.keepAlive ) {
+                        if (reg) {
+                            if (log.isDebugEnabled()) {
+                                log.debug("Connection is keep alive, registering back for OP_READ");
+                            }
+                            if (event) {
+                                this.add(attachment.getChannel(),SelectionKey.OP_READ);
+                            } else {
+                                reg(sk,attachment,SelectionKey.OP_READ);
+                            }
+                        }
+                    } else {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Send file connection is being closed");
+                        }
+                        cancelledKey(sk,SocketStatus.STOP,false);
+                    }
+                } else if ( attachment.interestOps() == 0 && reg ) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("OP_WRITE for sendilfe:"+sd.fileName);
+                    }
+                    if (event) {
+                        add(attachment.getChannel(),SelectionKey.OP_WRITE);
+                    } else {
+                        reg(sk,attachment,SelectionKey.OP_WRITE);
+                    }
+                }
+            }catch ( IOException x ) {
+                if ( log.isDebugEnabled() ) log.debug("Unable to complete sendfile request:", x);
+                cancelledKey(sk,SocketStatus.ERROR,false);
+                return false;
+            }catch ( Throwable t ) {
+                log.error("",t);
+                cancelledKey(sk, SocketStatus.ERROR, false);
+                return false;
+            }finally {
+                if (sc!=null) sc.setSendFile(false);
+            }
+            return true;
+        }
+
+        protected void unreg(SelectionKey sk, KeyAttachment attachment, int readyOps) {
+            //this is a must, so that we don't have multiple threads messing with the socket
+            reg(sk,attachment,sk.interestOps()& (~readyOps));
+        }
+        
+        protected void reg(SelectionKey sk, KeyAttachment attachment, int intops) {
+            sk.interestOps(intops); 
+            attachment.interestOps(intops);
+            attachment.setCometOps(intops);
+        }
+
+        protected void timeout(int keyCount, boolean hasEvents) {
+            long now = System.currentTimeMillis();
+            //don't process timeouts too frequently, but if the selector simply timed out
+            //then we can check timeouts to avoid gaps
+            if ( ((keyCount>0 || hasEvents) ||(now < nextExpiration)) && (!close) ) {
+                return;
+            }
+            long prevExp = nextExpiration; //for logging purposes only
+            nextExpiration = now + socketProperties.getTimeoutInterval();
+            //timeout
+            Set<SelectionKey> keys = selector.keys();
+            int keycount = 0;
+            for (Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext();) {
+                SelectionKey key = iter.next();
+                keycount++;
+                try {
+                    KeyAttachment ka = (KeyAttachment) key.attachment();
+                    if ( ka == null ) {
+                        cancelledKey(key, SocketStatus.ERROR,false); //we don't support any keys without attachments
+                    } else if ( ka.getError() ) {
+                        cancelledKey(key, SocketStatus.ERROR,true);//TODO this is not yet being used
+                    } else if (ka.getComet() && ka.getCometNotify() ) {
+                        ka.setCometNotify(false);
+                        reg(key,ka,0);//avoid multiple calls, this gets reregistered after invocation
+                        //if (!processSocket(ka.getChannel(), SocketStatus.OPEN_CALLBACK)) processSocket(ka.getChannel(), SocketStatus.DISCONNECT);
+                        if (!processSocket(ka.getChannel(), SocketStatus.OPEN, true)) processSocket(ka.getChannel(), SocketStatus.DISCONNECT, true);
+                    } else if ((ka.interestOps()&SelectionKey.OP_READ) == SelectionKey.OP_READ ||
+                              (ka.interestOps()&SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
+                        //only timeout sockets that we are waiting for a read from
+                        long delta = now - ka.getLastAccess();
+                        long timeout = (ka.getTimeout()==-1)?((long) socketProperties.getSoTimeout()):(ka.getTimeout());
+                        boolean isTimedout = delta > timeout;
+                        if ( close ) {
+                            key.interestOps(0); 
+                            ka.interestOps(0); //avoid duplicate stop calls
+                            processKey(key,ka);
+                        } else if (isTimedout) {
+                            key.interestOps(0); 
+                            ka.interestOps(0); //avoid duplicate timeout calls
+                            cancelledKey(key, SocketStatus.TIMEOUT,true);
+                        } else {
+                            long nextTime = now+(timeout-delta);
+                            nextExpiration = (nextTime < nextExpiration)?nextTime:nextExpiration;
+                        }
+                    } else if (ka.isAsync() || ka.getComet()) {
+                        long delta = now - ka.getLastAccess();
+                        long timeout = (ka.getTimeout()==-1)?((long) socketProperties.getSoTimeout()):(ka.getTimeout());
+                        boolean isTimedout = delta > timeout;
+                        if (isTimedout) {
+                            // Prevent subsequent timeouts if the timeout event takes a while to process
+                            ka.access(Long.MAX_VALUE);
+                            processSocket(ka.getChannel(), SocketStatus.TIMEOUT, true);
+                        }
+                    }//end if
+                }catch ( CancelledKeyException ckx ) {
+                    cancelledKey(key, SocketStatus.ERROR,false);
+                }
+            }//for
+            if ( log.isTraceEnabled() ) log.trace("timeout completed: keys processed="+keycount+"; now="+now+"; nextExpiration="+prevExp+"; "+
+                                                  "keyCount="+keyCount+"; hasEvents="+hasEvents +"; eval="+( (now < prevExp) && (keyCount>0 || hasEvents) && (!close) ));
+
+        }
+    }
+
+// ----------------------------------------------------- Key Attachment Class   
+    public static class KeyAttachment extends SocketWrapper<NioChannel> {
+        
+        public KeyAttachment(NioChannel channel) {
+            super(channel);
+        }
+        
+        public void reset(Poller poller, NioChannel channel, long soTimeout) {
+            this.socket = channel;
+            this.poller = poller;
+            lastAccess = System.currentTimeMillis();
+            comet = false;
+            timeout = soTimeout;
+            error = false;
+            lastRegistered = 0;
+            sendfileData = null;
+            if ( readLatch!=null ) try {for (int i=0; i<(int)readLatch.getCount();i++) readLatch.countDown();}catch (Exception ignore){}
+            readLatch = null;
+            if ( writeLatch!=null ) try {for (int i=0; i<(int)writeLatch.getCount();i++) writeLatch.countDown();}catch (Exception ignore){}
+            writeLatch = null;
+            cometNotify = false;
+            cometOps = SelectionKey.OP_READ;
+            sendfileData = null;
+            keepAliveLeft = 100;
+            async = false;
+        }
+        
+        public void reset() {
+            reset(null,null,-1);
+        }
+        
+        public Poller getPoller() { return poller;}
+        public void setPoller(Poller poller){this.poller = poller;}
+        public void setComet(boolean comet) { this.comet = comet; }
+        public boolean getComet() { return comet; }
+        public void setCometNotify(boolean notify) { this.cometNotify = notify; }
+        public boolean getCometNotify() { return cometNotify; }
+        public void setCometOps(int ops) { this.cometOps = ops; }
+        public int getCometOps() { return cometOps; }
+        public NioChannel getChannel() { return getSocket();}
+        public void setChannel(NioChannel channel) { this.socket = channel;}
+        protected Poller poller = null;
+        protected int interestOps = 0;
+        public int interestOps() { return interestOps;}
+        public int interestOps(int ops) { this.interestOps  = ops; return ops; }
+        public CountDownLatch getReadLatch() { return readLatch; }
+        public CountDownLatch getWriteLatch() { return writeLatch; }
+        protected CountDownLatch resetLatch(CountDownLatch latch) {
+            if ( latch==null || latch.getCount() == 0 ) return null;
+            else throw new IllegalStateException("Latch must be at count 0");
+        }
+        public void resetReadLatch() { readLatch = resetLatch(readLatch); }
+        public void resetWriteLatch() { writeLatch = resetLatch(writeLatch); }
+        
+        protected CountDownLatch startLatch(CountDownLatch latch, int cnt) { 
+            if ( latch == null || latch.getCount() == 0 ) {
+                return new CountDownLatch(cnt);
+            }
+            else throw new IllegalStateException("Latch must be at count 0 or null.");
+        }
+        public void startReadLatch(int cnt) { readLatch = startLatch(readLatch,cnt);}
+        public void startWriteLatch(int cnt) { writeLatch = startLatch(writeLatch,cnt);}
+        
+        protected void awaitLatch(CountDownLatch latch, long timeout, TimeUnit unit) throws InterruptedException {
+            if ( latch == null ) throw new IllegalStateException("Latch cannot be null");
+            latch.await(timeout,unit);
+        }
+        public void awaitReadLatch(long timeout, TimeUnit unit) throws InterruptedException { awaitLatch(readLatch,timeout,unit);}
+        public void awaitWriteLatch(long timeout, TimeUnit unit) throws InterruptedException { awaitLatch(writeLatch,timeout,unit);}
+        
+        public long getLastRegistered() { return lastRegistered; }
+        public void setLastRegistered(long reg) { lastRegistered = reg; }
+        
+        public void setSendfileData(SendfileData sf) { this.sendfileData = sf;}
+        public SendfileData getSendfileData() { return this.sendfileData;}
+        
+        protected boolean comet = false;
+        protected int cometOps = SelectionKey.OP_READ;
+        protected boolean cometNotify = false;
+        protected CountDownLatch readLatch = null;
+        protected CountDownLatch writeLatch = null;
+        protected SendfileData sendfileData = null;
+        
+    }
+
+    // ------------------------------------------------ Application Buffer Handler
+    public static class NioBufferHandler implements ApplicationBufferHandler {
+        protected ByteBuffer readbuf = null;
+        protected ByteBuffer writebuf = null;
+        
+        public NioBufferHandler(int readsize, int writesize, boolean direct) {
+            if ( direct ) {
+                readbuf = ByteBuffer.allocateDirect(readsize);
+                writebuf = ByteBuffer.allocateDirect(writesize);
+            }else {
+                readbuf = ByteBuffer.allocate(readsize);
+                writebuf = ByteBuffer.allocate(writesize);
+            }
+        }
+        
+        @Override
+        public ByteBuffer expand(ByteBuffer buffer, int remaining) {return buffer;}
+        @Override
+        public ByteBuffer getReadBuffer() {return readbuf;}
+        @Override
+        public ByteBuffer getWriteBuffer() {return writebuf;}
+
+    }
+
+    // ------------------------------------------------ Handler Inner Interface
+
+
+    /**
+     * Bare bones interface used for socket processing. Per thread data is to be
+     * stored in the ThreadWithAttributes extra folders, or alternately in
+     * thread local fields.
+     */
+    public interface Handler extends AbstractEndpoint.Handler {
+        public SocketState process(NioChannel socket);
+        public SocketState event(NioChannel socket, SocketStatus status);
+        public void release(NioChannel socket);
+        public void release(SocketChannel socket);
+        public SSLImplementation getSslImplementation();
+    }
+
+
+    // ---------------------------------------------- SocketProcessor Inner Class
+    /**
+     * This class is the equivalent of the Worker, but will simply use in an
+     * external Executor thread pool.
+     */
+    protected class SocketProcessor implements Runnable {
+
+        protected NioChannel socket = null;
+        protected SocketStatus status = null; 
+
+        public SocketProcessor(NioChannel socket, SocketStatus status) {
+            reset(socket,status);
+        }
+        
+        public void reset(NioChannel socket, SocketStatus status) {
+            this.socket = socket;
+            this.status = status;
+        }
+         
+        @Override
+        public void run() {
+            boolean launch = false;
+            synchronized (socket) {
+                SelectionKey key = null;
+                try {
+                    key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+                    int handshake = -1;
+                    
+                    try {
+                        if (key!=null) handshake = socket.handshake(key.isReadable(), key.isWritable());
+                    }catch ( IOException x ) {
+                        handshake = -1;
+                        if ( log.isDebugEnabled() ) log.debug("Error during SSL handshake",x);
+                    }catch ( CancelledKeyException ckx ) {
+                        handshake = -1;
+                    }
+                    if ( handshake == 0 ) {
+                        SocketState state = SocketState.OPEN;
+                        // Process the request from this socket
+                        state = (status==null)?handler.process(socket):handler.event(socket,status);
+    
+                        if (state == SocketState.CLOSED) {
+                            // Close socket and pool
+                            try {
+                                KeyAttachment ka = null;
+                                if (key!=null) {
+                                    ka = (KeyAttachment) key.attachment();
+                                    if (ka!=null) ka.setComet(false);
+                                    socket.getPoller().cancelledKey(key, SocketStatus.ERROR, false);
+                                }
+                                if (socket!=null) nioChannels.offer(socket);
+                                socket = null;
+                                if ( ka!=null ) keyCache.offer(ka);
+                                ka = null;
+                            }catch ( Exception x ) {
+                                log.error("",x);
+                            }
+                        } else if (state == SocketState.ASYNC_END) {
+                            launch = true;
+                        }
+                    } else if (handshake == -1 ) {
+                        KeyAttachment ka = null;
+                        if (key!=null) {
+                            ka = (KeyAttachment) key.attachment();
+                            socket.getPoller().cancelledKey(key, SocketStatus.DISCONNECT, false);
+                        }
+                        if (socket!=null) nioChannels.offer(socket);
+                        socket = null;
+                        if ( ka!=null ) keyCache.offer(ka);
+                        ka = null;
+                    } else {
+                        final SelectionKey fk = key;
+                        final int intops = handshake;
+                        final KeyAttachment ka = (KeyAttachment)fk.attachment();
+                        ka.getPoller().add(socket,intops);
+                    }
+                }catch(CancelledKeyException cx) {
+                    socket.getPoller().cancelledKey(key,null,false);
+                } catch (OutOfMemoryError oom) {
+                    try {
+                        oomParachuteData = null;
+                        socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
+                        releaseCaches();
+                        log.error("", oom);
+                    }catch ( Throwable oomt ) {
+                        try {
+                            System.err.println(oomParachuteMsg);
+                            oomt.printStackTrace();
+                        }catch (Throwable letsHopeWeDontGetHere){
+                            ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
+                        }
+                    }
+                }catch ( Throwable t ) {
+                    log.error("",t);
+                    socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
+                } finally {
+                    if (launch) {
+                        try {
+                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
+                        } catch (NullPointerException npe) {
+                            if (running) {
+                                log.error(sm.getString("endpoint.launch.fail"),
+                                        npe);
+                            }
+                        }
+                    }
+                    socket = null;
+                    status = null;
+                    //return to cache
+                    processorCache.offer(this);
+                }
+            }
+        }
+    }
+
+    // ----------------------------------------------- SendfileData Inner Class
+    /**
+     * SendfileData class.
+     */
+    public static class SendfileData {
+        // File
+        public String fileName;
+        public FileChannel fchannel;
+        public long pos;
+        public long length;
+        // KeepAlive flag
+        public boolean keepAlive;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioSelectorPool.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioSelectorPool.java
new file mode 100644
index 0000000..13c1a2e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/NioSelectorPool.java
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.MutableInteger;
+
+/**
+ *
+ * Thread safe non blocking selector pool
+ * @author Filip Hanik
+ * @version 1.0
+ * @since 6.0
+ */
+
+public class NioSelectorPool {
+    
+    public NioSelectorPool() {
+    }
+    
+    private static final Log log = LogFactory.getLog(NioSelectorPool.class);
+
+    protected static final boolean SHARED =
+        Boolean.valueOf(System.getProperty("org.apache.tomcat.util.net.NioSelectorShared", "true")).booleanValue();
+    
+    protected NioBlockingSelector blockingSelector;
+    
+    protected Selector SHARED_SELECTOR;
+    
+    protected int maxSelectors = 200;
+    protected long sharedSelectorTimeout = 30000;
+    protected int maxSpareSelectors = -1;
+    protected boolean enabled = true;
+    protected AtomicInteger active = new AtomicInteger(0);
+    protected AtomicInteger spare = new AtomicInteger(0);
+    protected ConcurrentLinkedQueue<Selector> selectors =
+        new ConcurrentLinkedQueue<Selector>();
+
+    protected Selector getSharedSelector() throws IOException {
+        if (SHARED && SHARED_SELECTOR == null) {
+            synchronized ( NioSelectorPool.class ) {
+                if ( SHARED_SELECTOR == null )  {
+                    SHARED_SELECTOR = Selector.open();
+                    log.info("Using a shared selector for servlet write/read");
+                }
+            }
+        }
+        return  SHARED_SELECTOR;
+    }
+
+    public Selector get() throws IOException{
+        if ( SHARED ) {
+            return getSharedSelector();
+        }
+        if ( (!enabled) || active.incrementAndGet() >= maxSelectors ) {
+            if ( enabled ) active.decrementAndGet();
+            return null;
+        }
+        Selector s = null;
+        try {
+            s = selectors.size()>0?selectors.poll():null;
+            if (s == null) s = Selector.open();
+            else spare.decrementAndGet();
+
+        }catch (NoSuchElementException x ) {
+            try {s = Selector.open();}catch (IOException iox){}
+        } finally {
+            if ( s == null ) active.decrementAndGet();//we were unable to find a selector
+        }
+        return s;
+    }
+
+
+
+    public void put(Selector s) throws IOException {
+        if ( SHARED ) return;
+        if ( enabled ) active.decrementAndGet();
+        if ( enabled && (maxSpareSelectors==-1 || spare.get() < Math.min(maxSpareSelectors,maxSelectors)) ) {
+            spare.incrementAndGet();
+            selectors.offer(s);
+        }
+        else s.close();
+    }
+
+    public void close() throws IOException {
+        enabled = false;
+        Selector s;
+        while ( (s = selectors.poll()) != null ) s.close();
+        spare.set(0);
+        active.set(0);
+        if (blockingSelector!=null) {
+            blockingSelector.close();
+        }
+        if ( SHARED && getSharedSelector()!=null ) {
+            getSharedSelector().close();
+            SHARED_SELECTOR = null;
+        }
+    }
+
+    public void open() throws IOException {
+        enabled = true;
+        getSharedSelector();
+        if (SHARED) {
+            blockingSelector = new NioBlockingSelector();
+            blockingSelector.open(getSharedSelector());
+        }
+
+    }
+
+    /**
+     * Performs a blocking write using the bytebuffer for data to be written and a selector to block.
+     * If the <code>selector</code> parameter is null, then it will perform a busy write that could
+     * take up a lot of CPU cycles.
+     * @param buf ByteBuffer - the buffer containing the data, we will write as long as <code>(buf.hasRemaining()==true)</code>
+     * @param socket SocketChannel - the socket to write data to
+     * @param selector Selector - the selector to use for blocking, if null then a busy write will be initiated
+     * @param writeTimeout long - the timeout for this write operation in milliseconds, -1 means no timeout
+     * @return int - returns the number of bytes written
+     * @throws EOFException if write returns -1
+     * @throws SocketTimeoutException if the write times out
+     * @throws IOException if an IO Exception occurs in the underlying socket logic
+     */
+    public int write(ByteBuffer buf, NioChannel socket, Selector selector, long writeTimeout) throws IOException {
+        return write(buf,socket,selector,writeTimeout,true,null);
+    }
+    
+    public int write(ByteBuffer buf, NioChannel socket, Selector selector, 
+                     long writeTimeout, boolean block,MutableInteger lastWrite) throws IOException {
+        if ( SHARED && block ) {
+            return blockingSelector.write(buf,socket,writeTimeout,lastWrite);
+        }
+        SelectionKey key = null;
+        int written = 0;
+        boolean timedout = false;
+        int keycount = 1; //assume we can write
+        long time = System.currentTimeMillis(); //start the timeout timer
+        try {
+            while ( (!timedout) && buf.hasRemaining() ) {
+                int cnt = 0;
+                if ( keycount > 0 ) { //only write if we were registered for a write
+                    cnt = socket.write(buf); //write the data
+                    if (lastWrite!=null) lastWrite.set(cnt);
+                    if (cnt == -1) throw new EOFException();
+                    
+                    written += cnt;
+                    if (cnt > 0) {
+                        time = System.currentTimeMillis(); //reset our timeout timer
+                        continue; //we successfully wrote, try again without a selector
+                    }
+                    if (cnt==0 && (!block)) break; //don't block
+                }
+                if ( selector != null ) {
+                    //register OP_WRITE to the selector
+                    if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_WRITE);
+                    else key.interestOps(SelectionKey.OP_WRITE);
+                    keycount = selector.select(writeTimeout);
+                }
+                if (writeTimeout > 0 && (selector == null || keycount == 0) ) timedout = (System.currentTimeMillis()-time)>=writeTimeout;
+            }//while
+            if ( timedout ) throw new SocketTimeoutException();
+        } finally {
+            if (key != null) {
+                key.cancel();
+                if (selector != null) selector.selectNow();//removes the key from this selector
+            }
+        }
+        return written;
+    }
+
+    /**
+     * Performs a blocking read using the bytebuffer for data to be read and a selector to block.
+     * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+     * take up a lot of CPU cycles.
+     * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+     * @param socket SocketChannel - the socket to write data to
+     * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+     * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+     * @return int - returns the number of bytes read
+     * @throws EOFException if read returns -1
+     * @throws SocketTimeoutException if the read times out
+     * @throws IOException if an IO Exception occurs in the underlying socket logic
+     */
+    public int read(ByteBuffer buf, NioChannel socket, Selector selector, long readTimeout) throws IOException {
+        return read(buf,socket,selector,readTimeout,true);
+    }
+    /**
+     * Performs a read using the bytebuffer for data to be read and a selector to register for events should 
+     * you have the block=true.
+     * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+     * take up a lot of CPU cycles.
+     * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+     * @param socket SocketChannel - the socket to write data to
+     * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+     * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+     * @param block - true if you want to block until data becomes available or timeout time has been reached
+     * @return int - returns the number of bytes read
+     * @throws EOFException if read returns -1
+     * @throws SocketTimeoutException if the read times out
+     * @throws IOException if an IO Exception occurs in the underlying socket logic
+     */
+    public int read(ByteBuffer buf, NioChannel socket, Selector selector, long readTimeout, boolean block) throws IOException {
+        if ( SHARED && block ) {
+            return blockingSelector.read(buf,socket,readTimeout);
+        }
+        SelectionKey key = null;
+        int read = 0;
+        boolean timedout = false;
+        int keycount = 1; //assume we can write
+        long time = System.currentTimeMillis(); //start the timeout timer
+        try {
+            while ( (!timedout) ) {
+                int cnt = 0;
+                if ( keycount > 0 ) { //only read if we were registered for a read
+                    cnt = socket.read(buf);
+                    if (cnt == -1) throw new EOFException();
+                    read += cnt;
+                    if (cnt > 0) continue; //read some more
+                    if (cnt==0 && (read>0 || (!block) ) ) break; //we are done reading
+                } 
+                if ( selector != null ) {//perform a blocking read
+                    //register OP_WRITE to the selector
+                    if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_READ);
+                    else key.interestOps(SelectionKey.OP_READ);
+                    keycount = selector.select(readTimeout);
+                }
+                if (readTimeout > 0 && (selector == null || keycount == 0) ) timedout = (System.currentTimeMillis()-time)>=readTimeout;
+            }//while
+            if ( timedout ) throw new SocketTimeoutException();
+        } finally {
+            if (key != null) {
+                key.cancel();
+                if (selector != null) selector.selectNow();//removes the key from this selector
+            }
+        }
+        return read;
+    }
+
+    public void setMaxSelectors(int maxSelectors) {
+        this.maxSelectors = maxSelectors;
+    }
+
+    public void setMaxSpareSelectors(int maxSpareSelectors) {
+        this.maxSpareSelectors = maxSpareSelectors;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public void setSharedSelectorTimeout(long sharedSelectorTimeout) {
+        this.sharedSelectorTimeout = sharedSelectorTimeout;
+    }
+
+    public int getMaxSelectors() {
+        return maxSelectors;
+    }
+
+    public int getMaxSpareSelectors() {
+        return maxSpareSelectors;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public long getSharedSelectorTimeout() {
+        return sharedSelectorTimeout;
+    }
+
+    public ConcurrentLinkedQueue<Selector> getSelectors() {
+        return selectors;
+    }
+
+    public AtomicInteger getSpare() {
+        return spare;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLImplementation.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLImplementation.java
new file mode 100644
index 0000000..f79b39f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLImplementation.java
@@ -0,0 +1,91 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+import java.net.Socket;
+
+import javax.net.ssl.SSLSession;
+
+/* SSLImplementation:
+
+ Abstract factory and base class for all SSL implementations.
+
+ @author EKR
+ */
+public abstract class SSLImplementation {
+    private static final org.apache.juli.logging.Log logger = org.apache.juli.logging.LogFactory
+            .getLog(SSLImplementation.class);
+
+    // The default implementations in our search path
+    private static final String JSSEImplementationClass =
+        "org.apache.tomcat.util.net.jsse.JSSEImplementation";
+
+    private static final String[] implementations = { JSSEImplementationClass };
+
+    public static SSLImplementation getInstance() throws ClassNotFoundException {
+        for (int i = 0; i < implementations.length; i++) {
+            try {
+                SSLImplementation impl = getInstance(implementations[i]);
+                return impl;
+            } catch (Exception e) {
+                if (logger.isTraceEnabled())
+                    logger.trace("Error creating " + implementations[i], e);
+            }
+        }
+
+        // If we can't instantiate any of these
+        throw new ClassNotFoundException("Can't find any SSL implementation");
+    }
+
+    public static SSLImplementation getInstance(String className)
+            throws ClassNotFoundException {
+        if (className == null)
+            return getInstance();
+
+        try {
+            // Workaround for the J2SE 1.4.x classloading problem (under
+            // Solaris).
+            // Class.forName(..) fails without creating class using new.
+            // This is an ugly workaround.
+            if (JSSEImplementationClass.equals(className)) {
+                return new org.apache.tomcat.util.net.jsse.JSSEImplementation();
+            }
+            Class<?> clazz = Class.forName(className);
+            return (SSLImplementation) clazz.newInstance();
+        } catch (Exception e) {
+            if (logger.isDebugEnabled())
+                logger
+                        .debug("Error loading SSL Implementation " + className,
+                                e);
+            throw new ClassNotFoundException(
+                    "Error loading SSL Implementation " + className + " :"
+                            + e.toString());
+        }
+    }
+
+    public abstract String getImplementationName();
+
+    public abstract ServerSocketFactory getServerSocketFactory(
+            AbstractEndpoint endpoint);
+
+    public abstract SSLSupport getSSLSupport(Socket sock);
+
+    public abstract SSLSupport getSSLSupport(SSLSession session);
+
+    public abstract SSLUtil getSSLUtil(AbstractEndpoint ep);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLSessionManager.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLSessionManager.java
new file mode 100644
index 0000000..0e1b410
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLSessionManager.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+/**
+ * Defines an interface used to manage SSL sessions. The manager operates on a
+ * single session.
+ * 
+ * $Id: SSLSessionManager.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+public interface SSLSessionManager {
+    /**
+     * Invalidate the SSL session
+     */
+    public void invalidateSession();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLSupport.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLSupport.java
new file mode 100644
index 0000000..e569ca5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLSupport.java
@@ -0,0 +1,137 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+import java.io.IOException;
+
+/**
+ * Defines an interface to interact with SSL sessions.
+ */
+public interface SSLSupport {
+    /**
+     * The Request attribute key for the cipher suite.
+     */
+    public static final String CIPHER_SUITE_KEY =
+            "javax.servlet.request.cipher_suite";
+
+    /**
+     * The Request attribute key for the key size.
+     */
+    public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size";
+
+    /**
+     * The Request attribute key for the client certificate chain.
+     */
+    public static final String CERTIFICATE_KEY =
+            "javax.servlet.request.X509Certificate";
+
+    /**
+     * The Request attribute key for the session id.
+     * This one is a Tomcat extension to the Servlet spec.
+     */
+    public static final String SESSION_ID_KEY =
+            "javax.servlet.request.ssl_session";
+
+    /**
+     * The request attribute key for the session manager.
+     * This one is a Tomcat extension to the Servlet spec.
+     */
+    public static final String SESSION_MGR =
+            "javax.servlet.request.ssl_session_mgr";
+
+    
+    /**
+     * A mapping table to determine the number of effective bits in the key
+     * when using a cipher suite containing the specified cipher name.  The
+     * underlying data came from the TLS Specification (RFC 2246), Appendix C.
+     */
+     static final CipherData ciphers[] = {
+        new CipherData("_WITH_NULL_", 0),
+        new CipherData("_WITH_IDEA_CBC_", 128),
+        new CipherData("_WITH_RC2_CBC_40_", 40),
+        new CipherData("_WITH_RC4_40_", 40),
+        new CipherData("_WITH_RC4_128_", 128),
+        new CipherData("_WITH_DES40_CBC_", 40),
+        new CipherData("_WITH_DES_CBC_", 56),
+        new CipherData("_WITH_3DES_EDE_CBC_", 168),
+        new CipherData("_WITH_AES_128_CBC_", 128),
+        new CipherData("_WITH_AES_256_CBC_", 256)
+    };
+
+    /**
+     * The cipher suite being used on this connection.
+     */
+    public String getCipherSuite() throws IOException;
+
+    /**
+     * The client certificate chain (if any).
+     */
+    public Object[] getPeerCertificateChain()
+        throws IOException;
+
+    /**
+     * The client certificate chain (if any).
+     * @param force If <code>true</code>, then re-negotiate the 
+     *              connection if necessary.
+     */
+    public Object[] getPeerCertificateChain(boolean force)
+        throws IOException;
+
+    /**
+     * Get the keysize.
+     *
+     * What we're supposed to put here is ill-defined by the
+     * Servlet spec (S 4.7 again). There are at least 4 potential
+     * values that might go here:
+     *
+     * (a) The size of the encryption key
+     * (b) The size of the MAC key
+     * (c) The size of the key-exchange key
+     * (d) The size of the signature key used by the server
+     *
+     * Unfortunately, all of these values are nonsensical.
+     **/
+    public Integer getKeySize()
+        throws IOException;
+
+    /**
+     * The current session Id.
+     */
+    public String getSessionId()
+        throws IOException;
+    /**
+     * Simple data class that represents the cipher being used, along with the
+     * corresponding effective key size.  The specified phrase must appear in the
+     * name of the cipher suite to be recognized.
+     */
+    
+    final class CipherData {
+    
+        public String phrase = null;
+    
+        public int keySize = 0;
+    
+        public CipherData(String phrase, int keySize) {
+            this.phrase = phrase;
+            this.keySize = keySize;
+        }
+    
+    }
+    
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLUtil.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLUtil.java
new file mode 100644
index 0000000..01fca48
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SSLUtil.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.TrustManager;
+
+public interface SSLUtil {
+
+    public SSLContext createSSLContext() throws Exception; 
+
+    public KeyManager[] getKeyManagers() throws Exception;
+    
+    public TrustManager[] getTrustManagers() throws Exception;
+    
+    public void configureSessionContext(SSLSessionContext sslSessionContext);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SecureNioChannel.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SecureNioChannel.java
new file mode 100644
index 0000000..4a68854
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SecureNioChannel.java
@@ -0,0 +1,562 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+
+import org.apache.tomcat.util.MutableInteger;
+
+/**
+ * 
+ * Implementation of a secure socket channel
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public class SecureNioChannel extends NioChannel  {
+    
+    protected ByteBuffer netInBuffer;
+    protected ByteBuffer netOutBuffer;
+    
+    protected SSLEngine sslEngine;
+    
+    protected boolean handshakeComplete = false;
+    protected HandshakeStatus handshakeStatus; //gets set by handshake
+    
+    protected boolean closed = false;
+    protected boolean closing = false;
+    
+    protected NioSelectorPool pool;
+    
+    public SecureNioChannel(SocketChannel channel, SSLEngine engine, 
+                            ApplicationBufferHandler bufHandler, NioSelectorPool pool) throws IOException {
+        super(channel,bufHandler);
+        this.sslEngine = engine;
+        int appBufSize = sslEngine.getSession().getApplicationBufferSize();
+        int netBufSize = sslEngine.getSession().getPacketBufferSize();
+        //allocate network buffers - TODO, add in optional direct non-direct buffers
+        if ( netInBuffer == null ) netInBuffer = ByteBuffer.allocateDirect(netBufSize);
+        if ( netOutBuffer == null ) netOutBuffer = ByteBuffer.allocateDirect(netBufSize);
+        
+        //selector pool for blocking operations
+        this.pool = pool;
+        
+        //ensure that the application has a large enough read/write buffers
+        //by doing this, we should not encounter any buffer overflow errors
+        bufHandler.expand(bufHandler.getReadBuffer(), appBufSize);
+        bufHandler.expand(bufHandler.getWriteBuffer(), appBufSize);
+        reset();
+    }
+    
+    public void reset(SSLEngine engine) throws IOException {
+        this.sslEngine = engine;
+        reset();
+    }
+    @Override
+    public void reset() throws IOException {
+        super.reset();
+        netOutBuffer.position(0);
+        netOutBuffer.limit(0);
+        netInBuffer.position(0);
+        netInBuffer.limit(0);
+        handshakeComplete = false;
+        closed = false;
+        closing = false;
+        //initiate handshake
+        sslEngine.beginHandshake();
+        handshakeStatus = sslEngine.getHandshakeStatus();
+    }
+    
+    @Override
+    public int getBufferSize() {
+        int size = super.getBufferSize();
+        size += netInBuffer!=null?netInBuffer.capacity():0;
+        size += netOutBuffer!=null?netOutBuffer.capacity():0;
+        return size;
+    }
+
+    
+//===========================================================================================    
+//                  NIO SSL METHODS
+//===========================================================================================
+    /**
+     * returns true if the network buffer has 
+     * been flushed out and is empty
+     * @return boolean
+     */
+    @Override
+    public boolean flush(boolean block, Selector s, long timeout,MutableInteger lastWrite) throws IOException {
+        if (!block) {
+            flush(netOutBuffer);
+        } else {
+            pool.write(netOutBuffer, this, s, timeout,block,lastWrite);
+        }
+        return !netOutBuffer.hasRemaining();
+    }
+    
+    /**
+     * Flushes the buffer to the network, non blocking
+     * @param buf ByteBuffer
+     * @return boolean true if the buffer has been emptied out, false otherwise
+     * @throws IOException
+     */
+    protected boolean flush(ByteBuffer buf) throws IOException {
+        int remaining = buf.remaining();
+        if ( remaining > 0 ) {
+            int written = sc.write(buf);
+            return written >= remaining;
+        }else {
+            return true;
+        }
+    }
+    
+    /**
+     * Performs SSL handshake, non blocking, but performs NEED_TASK on the same thread.<br>
+     * Hence, you should never call this method using your Acceptor thread, as you would slow down
+     * your system significantly.<br>
+     * The return for this operation is 0 if the handshake is complete and a positive value if it is not complete.
+     * In the event of a positive value coming back, reregister the selection key for the return values interestOps.
+     * @param read boolean - true if the underlying channel is readable
+     * @param write boolean - true if the underlying channel is writable
+     * @return int - 0 if hand shake is complete, otherwise it returns a SelectionKey interestOps value
+     * @throws IOException
+     */
+    @Override
+    public int handshake(boolean read, boolean write) throws IOException {
+        if ( handshakeComplete ) return 0; //we have done our initial handshake
+        
+        if (!flush(netOutBuffer)) return SelectionKey.OP_WRITE; //we still have data to write
+        
+        SSLEngineResult handshake = null;
+        
+        while (!handshakeComplete) {
+            switch ( handshakeStatus ) {
+                case NOT_HANDSHAKING: {
+                    //should never happen
+                    throw new IOException("NOT_HANDSHAKING during handshake");
+                }
+                case FINISHED: {
+                    //we are complete if we have delivered the last package
+                    handshakeComplete = !netOutBuffer.hasRemaining();
+                    //return 0 if we are complete, otherwise we still have data to write
+                    return handshakeComplete?0:SelectionKey.OP_WRITE; 
+                }
+                case NEED_WRAP: {
+                    //perform the wrap function
+                    handshake = handshakeWrap(write);
+                    if ( handshake.getStatus() == Status.OK ){
+                        if (handshakeStatus == HandshakeStatus.NEED_TASK) 
+                            handshakeStatus = tasks();
+                    } else {
+                        //wrap should always work with our buffers
+                        throw new IOException("Unexpected status:" + handshake.getStatus() + " during handshake WRAP.");
+                    }
+                    if ( handshakeStatus != HandshakeStatus.NEED_UNWRAP || (!flush(netOutBuffer)) ) {
+                        //should actually return OP_READ if we have NEED_UNWRAP
+                        return SelectionKey.OP_WRITE;
+                    }
+                    //fall down to NEED_UNWRAP on the same call, will result in a 
+                    //BUFFER_UNDERFLOW if it needs data
+                }
+                //$FALL-THROUGH$
+                case NEED_UNWRAP: {
+                    //perform the unwrap function
+                    handshake = handshakeUnwrap(read);
+                    if ( handshake.getStatus() == Status.OK ) {
+                        if (handshakeStatus == HandshakeStatus.NEED_TASK) 
+                            handshakeStatus = tasks();
+                    } else if ( handshake.getStatus() == Status.BUFFER_UNDERFLOW ){
+                        //read more data, reregister for OP_READ
+                        return SelectionKey.OP_READ;
+                    } else {
+                        throw new IOException("Invalid handshake status:"+handshakeStatus+" during handshake UNWRAP.");
+                    }//switch
+                    break;
+                }
+                case NEED_TASK: {
+                    handshakeStatus = tasks();
+                    break;
+                }
+                default: throw new IllegalStateException("Invalid handshake status:"+handshakeStatus);
+            }//switch
+        }//while      
+        //return 0 if we are complete, otherwise reregister for any activity that 
+        //would cause this method to be called again.
+        return handshakeComplete?0:(SelectionKey.OP_WRITE|SelectionKey.OP_READ);
+    }
+    
+    /**
+     * Force a blocking handshake to take place for this key.
+     * This requires that both network and application buffers have been emptied out prior to this call taking place, or a 
+     * IOException will be thrown.
+     * @param timeout - timeout in milliseconds for each socket operation
+     * @throws IOException - if an IO exception occurs or if application or network buffers contain data
+     * @throws SocketTimeoutException - if a socket operation timed out
+     */
+    public void rehandshake(long timeout) throws IOException {
+        //validate the network buffers are empty
+        if (netInBuffer.position() > 0 && netInBuffer.position()<netInBuffer.limit()) throw new IOException("Network input buffer still contains data. Handshake will fail.");
+        if (netOutBuffer.position() > 0 && netOutBuffer.position()<netOutBuffer.limit()) throw new IOException("Network output buffer still contains data. Handshake will fail.");
+        if (getBufHandler().getReadBuffer().position()>0 && getBufHandler().getReadBuffer().position()<getBufHandler().getReadBuffer().limit()) throw new IOException("Application input buffer still contains data. Data would have been lost.");
+        if (getBufHandler().getWriteBuffer().position()>0 && getBufHandler().getWriteBuffer().position()<getBufHandler().getWriteBuffer().limit()) throw new IOException("Application output buffer still contains data. Data would have been lost.");
+        reset();
+        boolean isReadable = true;
+        boolean isWriteable = true;
+        boolean handshaking = true;
+        Selector selector = null;
+        SelectionKey key = null;
+        try {
+            while (handshaking) {
+                int hsStatus = this.handshake(isReadable, isWriteable);
+                switch (hsStatus) {
+                    case -1 : throw new EOFException("EOF during handshake.");
+                    case  0 : handshaking = false; break;
+                    default : {
+                        long now = System.currentTimeMillis();
+                        if (selector==null) {
+                            selector = Selector.open();
+                            key = getIOChannel().register(selector, hsStatus);
+                        } else {
+                            key.interestOps(hsStatus);
+                        }
+                        int keyCount = selector.select(timeout);
+                        if (keyCount == 0 && ((System.currentTimeMillis()-now) >= timeout)) {
+                            throw new SocketTimeoutException("Handshake operation timed out.");
+                        }
+                        isReadable = key.isReadable();
+                        isWriteable = key.isWritable();
+                    }
+                }
+            }
+        } catch (IOException x) {
+            throw x;
+        } catch (Exception cx) {
+            IOException x = new IOException(cx);
+            throw x;
+        } finally {
+            if (key!=null) try {key.cancel();} catch (Exception ignore) {}
+            if (selector!=null) try {selector.close();} catch (Exception ignore) {}
+        }
+    }
+    
+    
+    
+    /**
+     * Executes all the tasks needed on the same thread.
+     * @return HandshakeStatus
+     */
+    protected SSLEngineResult.HandshakeStatus tasks() {
+        Runnable r = null;
+        while ( (r = sslEngine.getDelegatedTask()) != null) {
+            r.run();
+        }
+        return sslEngine.getHandshakeStatus();
+    }
+
+    /**
+     * Performs the WRAP function
+     * @param doWrite boolean
+     * @return SSLEngineResult
+     * @throws IOException
+     */
+    protected SSLEngineResult handshakeWrap(boolean doWrite) throws IOException {
+        //this should never be called with a network buffer that contains data
+        //so we can clear it here.
+        netOutBuffer.clear();
+        //perform the wrap
+        SSLEngineResult result = sslEngine.wrap(bufHandler.getWriteBuffer(), netOutBuffer);
+        //prepare the results to be written
+        netOutBuffer.flip();
+        //set the status
+        handshakeStatus = result.getHandshakeStatus();
+        //optimization, if we do have a writable channel, write it now
+        if ( doWrite ) flush(netOutBuffer);
+        return result;
+    }
+    
+    /**
+     * Perform handshake unwrap
+     * @param doread boolean
+     * @return SSLEngineResult
+     * @throws IOException
+     */
+    protected SSLEngineResult handshakeUnwrap(boolean doread) throws IOException {
+        
+        if (netInBuffer.position() == netInBuffer.limit()) {
+            //clear the buffer if we have emptied it out on data
+            netInBuffer.clear();
+        }
+        if ( doread )  {
+            //if we have data to read, read it
+            int read = sc.read(netInBuffer);
+            if (read == -1) throw new IOException("EOF encountered during handshake.");
+        }        
+        SSLEngineResult result;
+        boolean cont = false;
+        //loop while we can perform pure SSLEngine data
+        do {
+            //prepare the buffer with the incoming data
+            netInBuffer.flip();
+            //call unwrap
+            result = sslEngine.unwrap(netInBuffer, bufHandler.getReadBuffer());
+            //compact the buffer, this is an optional method, wonder what would happen if we didn't
+            netInBuffer.compact();
+            //read in the status
+            handshakeStatus = result.getHandshakeStatus();
+            if ( result.getStatus() == SSLEngineResult.Status.OK &&
+                 result.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) {
+                //execute tasks if we need to
+                handshakeStatus = tasks();
+            }
+            //perform another unwrap?
+            cont = result.getStatus() == SSLEngineResult.Status.OK &&
+                   handshakeStatus == HandshakeStatus.NEED_UNWRAP;
+        }while ( cont );
+        return result;
+    }
+    
+    /**
+     * Sends a SSL close message, will not physically close the connection here.<br>
+     * To close the connection, you could do something like
+     * <pre><code>
+     *   close();
+     *   while (isOpen() && !myTimeoutFunction()) Thread.sleep(25);
+     *   if ( isOpen() ) close(true); //forces a close if you timed out
+     * </code></pre>
+     * @throws IOException if an I/O error occurs
+     * @throws IOException if there is data on the outgoing network buffer and we are unable to flush it
+     * TODO Implement this java.io.Closeable method
+     */
+    @Override
+    public void close() throws IOException {
+        if (closing) return;
+        closing = true;
+        sslEngine.closeOutbound();
+
+        if (!flush(netOutBuffer)) {
+            throw new IOException("Remaining data in the network buffer, can't send SSL close message, force a close with close(true) instead");
+        }
+        //prep the buffer for the close message
+        netOutBuffer.clear();
+        //perform the close, since we called sslEngine.closeOutbound
+        SSLEngineResult handshake = sslEngine.wrap(getEmptyBuf(), netOutBuffer);
+        //we should be in a close state
+        if (handshake.getStatus() != SSLEngineResult.Status.CLOSED) {
+            throw new IOException("Invalid close state, will not send network data.");
+        }
+        //prepare the buffer for writing
+        netOutBuffer.flip();
+        //if there is data to be written
+        flush(netOutBuffer);
+
+        //is the channel closed?
+        closed = (!netOutBuffer.hasRemaining() && (handshake.getHandshakeStatus() != HandshakeStatus.NEED_WRAP));
+    }
+
+    /**
+     * Force a close, can throw an IOException
+     * @param force boolean
+     * @throws IOException
+     */
+    @Override
+    public void close(boolean force) throws IOException {
+        try {
+            close();
+        }finally {
+            if ( force || closed ) {
+                closed = true;
+                sc.socket().close();
+                sc.close();
+            }
+        }
+    }
+
+    /**
+     * Reads a sequence of bytes from this channel into the given buffer.
+     *
+     * @param dst The buffer into which bytes are to be transferred
+     * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached end-of-stream
+     * @throws IOException If some other I/O error occurs
+     * @throws IllegalArgumentException if the destination buffer is different than bufHandler.getReadBuffer()
+     * TODO Implement this java.nio.channels.ReadableByteChannel method
+     */
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+        //if we want to take advantage of the expand function, make sure we only use the ApplicationBufferHandler's buffers
+        if ( dst != bufHandler.getReadBuffer() ) throw new IllegalArgumentException("You can only read using the application read buffer provided by the handler.");
+        //are we in the middle of closing or closed?
+        if ( closing || closed) return -1;
+        //did we finish our handshake?
+        if (!handshakeComplete) throw new IllegalStateException("Handshake incomplete, you must complete handshake before reading data.");
+
+        //read from the network
+        int netread = sc.read(netInBuffer);
+        //did we reach EOF? if so send EOF up one layer.
+        if (netread == -1) return -1;
+        
+        //the data read
+        int read = 0;
+        //the SSL engine result
+        SSLEngineResult unwrap;
+        do {
+            //prepare the buffer
+            netInBuffer.flip();
+            //unwrap the data
+            unwrap = sslEngine.unwrap(netInBuffer, dst);
+            //compact the buffer
+            netInBuffer.compact();
+            
+            if ( unwrap.getStatus()==Status.OK || unwrap.getStatus()==Status.BUFFER_UNDERFLOW ) {
+                //we did receive some data, add it to our total
+                read += unwrap.bytesProduced();
+                //perform any tasks if needed
+                if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
+                //if we need more network data, then bail out for now.
+                if ( unwrap.getStatus() == Status.BUFFER_UNDERFLOW ) break;
+            }else if ( unwrap.getStatus()==Status.BUFFER_OVERFLOW && read>0 ) {
+                //buffer overflow can happen, if we have read data, then 
+                //empty out the dst buffer before we do another read
+                break;
+            }else {
+                //here we should trap BUFFER_OVERFLOW and call expand on the buffer
+                //for now, throw an exception, as we initialized the buffers
+                //in the constructor
+                throw new IOException("Unable to unwrap data, invalid status: " + unwrap.getStatus());
+            }
+        } while ( (netInBuffer.position() != 0)); //continue to unwrapping as long as the input buffer has stuff
+        return (read);
+    }
+
+    /**
+     * Writes a sequence of bytes to this channel from the given buffer.
+     *
+     * @param src The buffer from which bytes are to be retrieved
+     * @return The number of bytes written, possibly zero
+     * @throws IOException If some other I/O error occurs
+     * TODO Implement this java.nio.channels.WritableByteChannel method
+     */
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+        if ( src == this.netOutBuffer ) {
+            //we can get here through a recursive call
+            //by using the NioBlockingSelector
+            int written = sc.write(src);
+            return written;
+        } else {
+            //make sure we can handle expand, and that we only use on buffer
+            if ( (!this.isSendFile()) && (src != bufHandler.getWriteBuffer()) ) throw new IllegalArgumentException("You can only write using the application write buffer provided by the handler.");
+            //are we closing or closed?
+            if ( closing || closed) throw new IOException("Channel is in closing state.");
+
+            //the number of bytes written
+            int written = 0;
+
+            if (!flush(netOutBuffer)) {
+                //we haven't emptied out the buffer yet
+                return written;
+            }
+
+            /*
+             * The data buffer is empty, we can reuse the entire buffer.
+             */
+            netOutBuffer.clear();
+
+            SSLEngineResult result = sslEngine.wrap(src, netOutBuffer);
+            written = result.bytesConsumed();
+            netOutBuffer.flip();
+
+            if (result.getStatus() == Status.OK) {
+                if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
+            } else {
+                throw new IOException("Unable to wrap data, invalid engine state: " +result.getStatus());
+            }        
+
+            //force a flush
+            flush(netOutBuffer);
+
+            return written;
+        }
+    }
+    
+    @Override
+    public int getOutboundRemaining() {
+        return netOutBuffer.remaining();
+    }
+    
+    @Override
+    public boolean flushOutbound() throws IOException {
+        int remaining = netOutBuffer.remaining();
+        flush(netOutBuffer);
+        int remaining2= netOutBuffer.remaining();
+        return remaining2 < remaining;
+    }
+
+    
+    /**
+     * Callback interface to be able to expand buffers
+     * when buffer overflow exceptions happen
+     */
+    public static interface ApplicationBufferHandler {
+        public ByteBuffer expand(ByteBuffer buffer, int remaining);
+        public ByteBuffer getReadBuffer();
+        public ByteBuffer getWriteBuffer();
+    }
+
+    @Override
+    public ApplicationBufferHandler getBufHandler() {
+        return bufHandler;
+    }
+
+    @Override
+    public boolean isHandshakeComplete() {
+        return handshakeComplete;
+    }
+
+    @Override
+    public boolean isClosing() {
+        return closing;
+    }
+
+    public SSLEngine getSslEngine() {
+        return sslEngine;
+    }
+
+    public ByteBuffer getEmptyBuf() {
+        return emptyBuf;
+    }
+
+    public void setBufHandler(ApplicationBufferHandler bufHandler) {
+        this.bufHandler = bufHandler;
+    }
+    
+    @Override
+    public SocketChannel getIOChannel() {
+        return sc;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/ServerSocketFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/ServerSocketFactory.java
new file mode 100644
index 0000000..5d77925
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/ServerSocketFactory.java
@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * The common interface through which the {@link JIoEndpoint} interacts with
+ * both non-SSL and SSL sockets. 
+ */
+public interface ServerSocketFactory {
+
+    /**
+     * Returns a server socket which uses all network interfaces on the host,
+     * and is bound to a the specified port. The socket is configured with the
+     * socket options (such as accept timeout) given to this factory.
+     * 
+     * @param port
+     *            the port to listen to
+     * @exception IOException
+     *                for networking errors
+     * @exception InstantiationException
+     *                for construction errors
+     */
+    ServerSocket createSocket(int port) throws IOException,
+            InstantiationException;
+
+    /**
+     * Returns a server socket which uses all network interfaces on the host, is
+     * bound to a the specified port, and uses the specified connection backlog.
+     * The socket is configured with the socket options (such as accept timeout)
+     * given to this factory.
+     * 
+     * @param port
+     *            the port to listen to
+     * @param backlog
+     *            how many connections are queued
+     * @exception IOException
+     *                for networking errors
+     * @exception InstantiationException
+     *                for construction errors
+     */
+    ServerSocket createSocket(int port, int backlog) throws IOException,
+            InstantiationException;
+
+    /**
+     * Returns a server socket which uses only the specified network interface
+     * on the local host, is bound to a the specified port, and uses the
+     * specified connection backlog. The socket is configured with the socket
+     * options (such as accept timeout) given to this factory.
+     * 
+     * @param port
+     *            the port to listen to
+     * @param backlog
+     *            how many connections are queued
+     * @param ifAddress
+     *            the network interface address to use
+     * @exception IOException
+     *                for networking errors
+     * @exception InstantiationException
+     *                for construction errors
+     */
+    ServerSocket createSocket(int port, int backlog, InetAddress ifAddress)
+            throws IOException, InstantiationException;
+
+    /**
+     * Wrapper function for accept(). This allows us to trap and translate
+     * exceptions if necessary.
+     * 
+     * @exception IOException
+     */
+    Socket acceptSocket(ServerSocket socket) throws IOException;
+
+    /**
+     * Triggers the SSL handshake. This will be a no-op for non-SSL sockets.
+     * 
+     * @exception IOException
+     */
+    void handshake(Socket sock) throws IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketProperties.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketProperties.java
new file mode 100644
index 0000000..66a2101
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketProperties.java
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * Properties that can be set in the &lt;Connector&gt; element
+ * in server.xml. All properties are prefixed with &quot;socket.&quot;
+ * and are currently only working for the Nio connector
+ *
+ * @author Filip Hanik
+ */
+public class SocketProperties {
+    /**
+     * Enable/disable key cache, this bounded cache stores
+     * KeyAttachment objects to reduce GC
+     * Default is 500
+     * -1 is unlimited
+     * 0 is disabled
+     */
+    protected int keyCache = 500;
+    
+    /**
+     * Enable/disable socket processor cache, this bounded cache stores
+     * SocketProcessor objects to reduce GC
+     * Default is 500
+     * -1 is unlimited
+     * 0 is disabled
+     */
+    protected int processorCache = 500;
+
+    /**
+     * Enable/disable poller event cache, this bounded cache stores
+     * PollerEvent objects to reduce GC for the poller
+     * Default is 500 
+     * -1 is unlimited
+     * 0 is disabled
+     * >0 the max number of objects to keep in cache.
+     */
+    protected int eventCache = 500;
+
+    /**
+     * Enable/disable direct buffers for the network buffers
+     * Default value is enabled
+     */
+    protected boolean directBuffer = false;
+
+    /**
+     * Socket receive buffer size in bytes (SO_RCVBUF).
+     * JVM default used if not set.
+     */
+    protected Integer rxBufSize = null;
+
+    /**
+     * Socket send buffer size in bytes (SO_SNDBUF).
+     * JVM default used if not set.
+     */
+    protected Integer txBufSize = null;
+
+    /**
+     * The application read buffer size in bytes.
+     * Default value is rxBufSize
+     */
+    protected int appReadBufSize = 8192;
+
+    /**
+     * The application write buffer size in bytes
+     * Default value is txBufSize
+     */
+    protected int appWriteBufSize = 8192;
+
+    /**
+     * NioChannel pool size for the endpoint,
+     * this value is how many channels
+     * -1 means unlimited cached, 0 means no cache
+     * Default value is 500
+     */
+    protected int bufferPool = 500;
+
+    /**
+     * Buffer pool size in bytes to be cached
+     * -1 means unlimited, 0 means no cache
+     * Default value is 100MB (1024*1024*100 bytes)
+     */
+    protected int bufferPoolSize = 1024*1024*100;
+
+    /**
+     * TCP_NO_DELAY option. JVM default used if not set.
+     */
+    protected Boolean tcpNoDelay = Boolean.TRUE;
+
+    /**
+     * SO_KEEPALIVE option. JVM default used if not set.
+     */
+    protected Boolean soKeepAlive = null;
+
+    /**
+     * OOBINLINE option. JVM default used if not set.
+     */
+    protected Boolean ooBInline = null;
+
+    /**
+     * SO_REUSEADDR option. JVM default used if not set.
+     */
+    protected Boolean soReuseAddress = null;
+
+    /**
+     * SO_LINGER option, paired with the <code>soLingerTime</code> value.
+     * JVM defaults used unless both attributes are set.
+     */
+    protected Boolean soLingerOn = null;
+
+    /**
+     * SO_LINGER option, paired with the <code>soLingerOn</code> value.
+     * JVM defaults used unless both attributes are set.
+     */
+    protected Integer soLingerTime = null;
+
+    /**
+     * SO_TIMEOUT option. default is 20000.
+     */
+    protected Integer soTimeout = new Integer(20000);
+
+    /**
+     * Traffic class option, value between 0 and 255
+     * IPTOS_LOWCOST (0x02)
+     * IPTOS_RELIABILITY (0x04)
+     * IPTOS_THROUGHPUT (0x08)
+     * IPTOS_LOWDELAY (0x10)
+     * JVM default used if not set
+     */
+    protected Integer soTrafficClass = null;
+
+    /**
+     * Performance preferences according to
+     * http://download.oracle.com/javase/1.5.0/docs/api/java/net/Socket.html#setPerformancePreferences(int,%20int,%20int)
+     * All three performance attributes must be set or the JVM defaults will be
+     * used.
+     */
+    protected Integer performanceConnectionTime = null;
+
+    /**
+     * Performance preferences according to
+     * http://download.oracle.com/javase/1.5.0/docs/api/java/net/Socket.html#setPerformancePreferences(int,%20int,%20int)
+     * All three performance attributes must be set or the JVM defaults will be
+     * used.
+     */
+    protected Integer performanceLatency = null;
+
+    /**
+     * Performance preferences according to
+     * http://download.oracle.com/javase/1.5.0/docs/api/java/net/Socket.html#setPerformancePreferences(int,%20int,%20int)
+     * All three performance attributes must be set or the JVM defaults will be
+     * used.
+     */
+    protected Integer performanceBandwidth = null;
+    
+    /**
+     * The minimum frequency of the timeout interval to avoid the 
+     * poller going boinkers during high traffic
+     */
+    protected long timeoutInterval = 1000;
+    
+    /**
+     * Timeout in milliseconds for an unlock to take place.
+     */
+    protected int unlockTimeout = 250;
+
+    public void setProperties(Socket socket) throws SocketException{
+        if (rxBufSize != null)
+            socket.setReceiveBufferSize(rxBufSize.intValue());
+        if (txBufSize != null)
+            socket.setSendBufferSize(txBufSize.intValue());
+        if (ooBInline !=null)
+            socket.setOOBInline(ooBInline.booleanValue());
+        if (soKeepAlive != null)
+            socket.setKeepAlive(soKeepAlive.booleanValue());
+        if (performanceConnectionTime != null && performanceLatency != null &&
+                performanceBandwidth != null)
+            socket.setPerformancePreferences(
+                    performanceConnectionTime.intValue(),
+                    performanceLatency.intValue(),
+                    performanceBandwidth.intValue());
+        if (soReuseAddress != null)
+            socket.setReuseAddress(soReuseAddress.booleanValue());
+        if (soLingerOn != null && soLingerTime != null)
+            socket.setSoLinger(soLingerOn.booleanValue(),
+                    soLingerTime.intValue());
+        if (soTimeout != null && soTimeout.intValue() >= 0)
+            socket.setSoTimeout(soTimeout.intValue());
+        if (tcpNoDelay != null)
+            socket.setTcpNoDelay(tcpNoDelay.booleanValue());
+        if (soTrafficClass != null)
+            socket.setTrafficClass(soTrafficClass.intValue());
+    }
+
+    public void setProperties(ServerSocket socket) throws SocketException{
+        if (rxBufSize != null)
+            socket.setReceiveBufferSize(rxBufSize.intValue());
+        if (performanceConnectionTime != null && performanceLatency != null &&
+                performanceBandwidth != null)
+            socket.setPerformancePreferences(
+                    performanceConnectionTime.intValue(),
+                    performanceLatency.intValue(),
+                    performanceBandwidth.intValue());
+        if (soReuseAddress != null)
+            socket.setReuseAddress(soReuseAddress.booleanValue());
+        if (soTimeout != null && soTimeout.intValue() >= 0)
+            socket.setSoTimeout(soTimeout.intValue());
+    }
+
+    
+    public boolean getDirectBuffer() {
+        return directBuffer;
+    }
+
+    public boolean getOoBInline() {
+        return ooBInline.booleanValue();
+    }
+
+    public int getPerformanceBandwidth() {
+        return performanceBandwidth.intValue();
+    }
+
+    public int getPerformanceConnectionTime() {
+        return performanceConnectionTime.intValue();
+    }
+
+    public int getPerformanceLatency() {
+        return performanceLatency.intValue();
+    }
+
+    public int getRxBufSize() {
+        return rxBufSize.intValue();
+    }
+
+    public boolean getSoKeepAlive() {
+        return soKeepAlive.booleanValue();
+    }
+
+    public boolean getSoLingerOn() {
+        return soLingerOn.booleanValue();
+    }
+
+    public int getSoLingerTime() {
+        return soLingerTime.intValue();
+    }
+
+    public boolean getSoReuseAddress() {
+        return soReuseAddress.booleanValue();
+    }
+
+    public int getSoTimeout() {
+        return soTimeout.intValue();
+    }
+
+    public int getSoTrafficClass() {
+        return soTrafficClass.intValue();
+    }
+
+    public boolean getTcpNoDelay() {
+        return tcpNoDelay.booleanValue();
+    }
+
+    public int getTxBufSize() {
+        return txBufSize.intValue();
+    }
+
+    public int getBufferPool() {
+        return bufferPool;
+    }
+
+    public int getBufferPoolSize() {
+        return bufferPoolSize;
+    }
+
+    public int getEventCache() {
+        return eventCache;
+    }
+
+    public int getKeyCache() {
+        return keyCache;
+    }
+
+    public int getAppReadBufSize() {
+        return appReadBufSize;
+    }
+
+    public int getAppWriteBufSize() {
+        return appWriteBufSize;
+    }
+
+    public int getProcessorCache() {
+        return processorCache;
+    }
+
+    public long getTimeoutInterval() {
+        return timeoutInterval;
+    }
+
+    public int getDirectBufferPool() {
+        return bufferPool;
+    }
+
+    public void setPerformanceConnectionTime(int performanceConnectionTime) {
+        this.performanceConnectionTime =
+            Integer.valueOf(performanceConnectionTime);
+    }
+
+    public void setTxBufSize(int txBufSize) {
+        this.txBufSize = Integer.valueOf(txBufSize);
+    }
+
+    public void setTcpNoDelay(boolean tcpNoDelay) {
+        this.tcpNoDelay = Boolean.valueOf(tcpNoDelay);
+    }
+
+    public void setSoTrafficClass(int soTrafficClass) {
+        this.soTrafficClass = Integer.valueOf(soTrafficClass);
+    }
+
+    public void setSoTimeout(int soTimeout) {
+        this.soTimeout = Integer.valueOf(soTimeout);
+    }
+
+    public void setSoReuseAddress(boolean soReuseAddress) {
+        this.soReuseAddress = Boolean.valueOf(soReuseAddress);
+    }
+
+    public void setSoLingerTime(int soLingerTime) {
+        this.soLingerTime = Integer.valueOf(soLingerTime);
+    }
+
+    public void setSoKeepAlive(boolean soKeepAlive) {
+        this.soKeepAlive = Boolean.valueOf(soKeepAlive);
+    }
+
+    public void setRxBufSize(int rxBufSize) {
+        this.rxBufSize = Integer.valueOf(rxBufSize);
+    }
+
+    public void setPerformanceLatency(int performanceLatency) {
+        this.performanceLatency = Integer.valueOf(performanceLatency);
+    }
+
+    public void setPerformanceBandwidth(int performanceBandwidth) {
+        this.performanceBandwidth = Integer.valueOf(performanceBandwidth);
+    }
+
+    public void setOoBInline(boolean ooBInline) {
+        this.ooBInline = Boolean.valueOf(ooBInline);
+    }
+
+    public void setDirectBuffer(boolean directBuffer) {
+        this.directBuffer = directBuffer;
+    }
+
+    public void setSoLingerOn(boolean soLingerOn) {
+        this.soLingerOn = Boolean.valueOf(soLingerOn);
+    }
+
+    public void setBufferPool(int bufferPool) {
+        this.bufferPool = bufferPool;
+    }
+
+    public void setBufferPoolSize(int bufferPoolSize) {
+        this.bufferPoolSize = bufferPoolSize;
+    }
+
+    public void setEventCache(int eventCache) {
+        this.eventCache = eventCache;
+    }
+
+    public void setKeyCache(int keyCache) {
+        this.keyCache = keyCache;
+    }
+
+    public void setAppReadBufSize(int appReadBufSize) {
+        this.appReadBufSize = appReadBufSize;
+    }
+
+    public void setAppWriteBufSize(int appWriteBufSize) {
+        this.appWriteBufSize = appWriteBufSize;
+    }
+
+    public void setProcessorCache(int processorCache) {
+        this.processorCache = processorCache;
+    }
+
+    public void setTimeoutInterval(long timeoutInterval) {
+        this.timeoutInterval = timeoutInterval;
+    }
+
+    public void setDirectBufferPool(int directBufferPool) {
+        this.bufferPool = directBufferPool;
+    }
+
+    public int getUnlockTimeout() {
+        return unlockTimeout;
+    }
+
+    public void setUnlockTimeout(int unlockTimeout) {
+        this.unlockTimeout = unlockTimeout;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketStatus.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketStatus.java
new file mode 100644
index 0000000..58ee7f1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketStatus.java
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+/**
+ * Someone, please change the enum name. 
+ * 
+ * @author remm
+ */
+public enum SocketStatus {
+    OPEN, STOP, TIMEOUT, DISCONNECT, ERROR
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketWrapper.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketWrapper.java
new file mode 100644
index 0000000..a0d11c1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/SocketWrapper.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.net;
+
+public class SocketWrapper<E> {
+    
+    protected volatile E socket;
+    
+    protected volatile long lastAccess = -1;
+    protected long timeout = -1;
+    protected boolean error = false;
+    protected long lastRegistered = 0;
+    protected volatile int keepAliveLeft = 100;
+    protected boolean async = false;
+    protected boolean keptAlive = false;
+    
+    public SocketWrapper(E socket) {
+        this.socket = socket;
+    }
+    
+    public E getSocket() {
+        return socket;
+    }
+    
+    public boolean isAsync() { return async; }
+    public void setAsync(boolean async) { this.async = async; }
+    public long getLastAccess() { return lastAccess; }
+    public void access() { access(System.currentTimeMillis()); }
+    public void access(long access) { lastAccess = access; }
+    public void setTimeout(long timeout) {this.timeout = timeout;}
+    public long getTimeout() {return this.timeout;}
+    public boolean getError() { return error; }
+    public void setError(boolean error) { this.error = error; }
+    public int getKeepAliveLeft() { return this.keepAliveLeft; }
+    public void setKeepAliveLeft(int keepAliveLeft) { this.keepAliveLeft = keepAliveLeft;}
+    public int decrementKeepAlive() { return (--keepAliveLeft);}
+    public boolean isKeptAlive() {return keptAlive;}
+    public void setKeptAlive(boolean keptAlive) {this.keptAlive = keptAlive;}
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/URL.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/URL.java
new file mode 100644
index 0000000..8365ad7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/URL.java
@@ -0,0 +1,745 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.util.Locale;
+
+
+/**
+ * <p><strong>URL</strong> is designed to provide public APIs for parsing
+ * and synthesizing Uniform Resource Locators as similar as possible to the
+ * APIs of <code>java.net.URL</code>, but without the ability to open a
+ * stream or connection.  One of the consequences of this is that you can
+ * construct URLs for protocols for which a URLStreamHandler is not
+ * available (such as an "https" URL when JSSE is not installed).</p>
+ *
+ * <p><strong>WARNING</strong> - This class assumes that the string
+ * representation of a URL conforms to the <code>spec</code> argument
+ * as described in RFC 2396 "Uniform Resource Identifiers: Generic Syntax":
+ * <pre>
+ *   &lt;scheme&gt;//&lt;authority&gt;&lt;path&gt;?&lt;query&gt;#&lt;fragment&gt;
+ * </pre></p>
+ *
+ * <p><strong>FIXME</strong> - This class really ought to end up in a Commons
+ * package someplace.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: URL.java,v 1.1 2011/06/28 21:14:16 rherrmann Exp $
+ */
+
+public final class URL implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Create a URL object from the specified String representation.
+     *
+     * @param spec String representation of the URL
+     *
+     * @exception MalformedURLException if the string representation
+     *  cannot be parsed successfully
+     */
+    public URL(String spec) throws MalformedURLException {
+
+        this(null, spec);
+
+    }
+
+
+    /**
+     * Create a URL object by parsing a string representation relative
+     * to a specified context.  Based on logic from JDK 1.3.1's
+     * <code>java.net.URL</code>.
+     *
+     * @param context URL against which the relative representation
+     *  is resolved
+     * @param spec String representation of the URL (usually relative)
+     *
+     * @exception MalformedURLException if the string representation
+     *  cannot be parsed successfully
+     */
+    public URL(URL context, String spec) throws MalformedURLException {
+
+        String original = spec;
+        int i, limit, c;
+        int start = 0;
+        String newProtocol = null;
+        boolean aRef = false;
+
+        try {
+
+            // Eliminate leading and trailing whitespace
+            limit = spec.length();
+            while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
+                limit--;
+            }
+            while ((start < limit) && (spec.charAt(start) <= ' ')) {
+                start++;
+            }
+
+            // If the string representation starts with "url:", skip it
+            if (spec.regionMatches(true, start, "url:", 0, 4)) {
+                start += 4;
+            }
+
+            // Is this a ref relative to the context URL?
+            if ((start < spec.length()) && (spec.charAt(start) == '#')) {
+                aRef = true;
+            }
+
+            // Parse out the new protocol
+            for (i = start; !aRef && (i < limit) ; i++) { 
+                c = spec.charAt(i);
+                if (c == ':') {
+                    String s = spec.substring(start, i).toLowerCase(Locale.ENGLISH);
+                    // Assume all protocols are valid
+                    newProtocol = s;
+                    start = i + 1;
+                    break;
+                } else if( c == '#' ) {
+                    aRef = true;
+                } else if( !isSchemeChar((char)c) ) {
+                    break;
+                }
+            }
+
+            // Only use our context if the protocols match
+            protocol = newProtocol;
+            if ((context != null) && ((newProtocol == null) ||
+                 newProtocol.equalsIgnoreCase(context.getProtocol()))) {
+                // If the context is a hierarchical URL scheme and the spec
+                // contains a matching scheme then maintain backwards
+                // compatibility and treat it as if the spec didn't contain
+                // the scheme; see 5.2.3 of RFC2396
+                if ((context.getPath() != null) &&
+                    (context.getPath().startsWith("/")))
+                    newProtocol = null;
+                if (newProtocol == null) {
+                    protocol = context.getProtocol();
+                    authority = context.getAuthority();
+                    userInfo = context.getUserInfo();
+                    host = context.getHost();
+                    port = context.getPort();
+                    file = context.getFile();
+                    int question = file.lastIndexOf("?");
+                    if (question < 0)
+                        path = file;
+                    else
+                        path = file.substring(0, question);
+                }
+            }
+
+            if (protocol == null)
+                throw new MalformedURLException("no protocol: " + original);
+
+            // Parse out any ref portion of the spec
+            i = spec.indexOf('#', start);
+            if (i >= 0) {
+                ref = spec.substring(i + 1, limit);
+                limit = i;
+            }
+
+            // Parse the remainder of the spec in a protocol-specific fashion
+            parse(spec, start, limit);
+            if (context != null)
+                normalize();
+
+
+        } catch (MalformedURLException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new MalformedURLException(e.toString());
+        }
+
+    }
+
+
+    /**
+     * Create a URL object from the specified components.  The default port
+     * number for the specified protocol will be used.
+     *
+     * @param protocol Name of the protocol to use
+     * @param host Name of the host addressed by this protocol
+     * @param file Filename on the specified host
+     *
+     * @exception MalformedURLException is never thrown, but present for
+     *  compatible APIs
+     */
+    public URL(String protocol, String host, String file)
+        throws MalformedURLException {
+
+        this(protocol, host, -1, file);
+
+    }
+
+
+    /**
+     * Create a URL object from the specified components.  Specifying a port
+     * number of -1 indicates that the URL should use the default port for
+     * that protocol.  Based on logic from JDK 1.3.1's
+     * <code>java.net.URL</code>.
+     *
+     * @param protocol Name of the protocol to use
+     * @param host Name of the host addressed by this protocol
+     * @param port Port number, or -1 for the default port for this protocol
+     * @param file Filename on the specified host
+     *
+     * @exception MalformedURLException is never thrown, but present for
+     *  compatible APIs
+     */
+    public URL(String protocol, String host, int port, String file)
+        throws MalformedURLException {
+
+        this.protocol = protocol;
+        this.host = host;
+        this.port = port;
+
+        int hash = file.indexOf('#');
+        this.file = hash < 0 ? file : file.substring(0, hash);
+        this.ref = hash < 0 ? null : file.substring(hash + 1);
+        int question = file.lastIndexOf('?');
+        if (question >= 0) {
+            query = file.substring(question + 1);
+            path = file.substring(0, question);
+        } else
+            path = file;
+
+        if ((host != null) && (host.length() > 0))
+            authority = (port == -1) ? host : host + ":" + port;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The authority part of the URL.
+     */
+    private String authority = null;
+
+
+    /**
+     * The filename part of the URL.
+     */
+    private String file = null;
+
+
+    /**
+     * The host name part of the URL.
+     */
+    private String host = null;
+
+
+    /**
+     * The path part of the URL.
+     */
+    private String path = null;
+
+
+    /**
+     * The port number part of the URL.
+     */
+    private int port = -1;
+
+
+    /**
+     * The protocol name part of the URL.
+     */
+    private String protocol = null;
+
+
+    /**
+     * The query part of the URL.
+     */
+    private String query = null;
+
+
+    /**
+     * The reference part of the URL.
+     */
+    private String ref = null;
+
+
+    /**
+     * The user info part of the URL.
+     */
+    private String userInfo = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Compare two URLs for equality.  The result is <code>true</code> if and
+     * only if the argument is not null, and is a <code>URL</code> object
+     * that represents the same <code>URL</code> as this object.  Two
+     * <code>URLs</code> are equal if they have the same protocol and
+     * reference the same host, the same port number on the host,
+     * and the same file and anchor on the host.
+     *
+     * @param obj The URL to compare against
+     */
+    @Override
+    public boolean equals(Object obj) {
+
+        if (!(obj instanceof URL))
+            return (false);
+        URL other = (URL) obj;
+        if (!sameFile(other))
+            return (false);
+        return (compare(ref, other.getRef()));
+
+    }
+
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((file == null) ? 0 : file.hashCode());
+        result = prime * result + ((host == null) ? 0 : host.hashCode());
+        result = prime * result + port;
+        result = prime * result +
+                ((protocol == null) ? 0 : protocol.hashCode());
+        result = prime * result + ((ref == null) ? 0 : ref.hashCode());
+        return result;
+    }
+
+
+    /**
+     * Return the authority part of the URL.
+     */
+    public String getAuthority() {
+
+        return (this.authority);
+
+    }
+
+
+    /**
+     * Return the filename part of the URL.  <strong>NOTE</strong> - For
+     * compatibility with <code>java.net.URL</code>, this value includes
+     * the query string if there was one.  For just the path portion,
+     * call <code>getPath()</code> instead.
+     */
+    public String getFile() {
+
+        if (file == null)
+            return ("");
+        return (this.file);
+
+    }
+
+
+    /**
+     * Return the host name part of the URL.
+     */
+    public String getHost() {
+
+        return (this.host);
+
+    }
+
+
+    /**
+     * Return the path part of the URL.
+     */
+    public String getPath() {
+
+        if (this.path == null)
+            return ("");
+        return (this.path);
+
+    }
+
+
+    /**
+     * Return the port number part of the URL.
+     */
+    public int getPort() {
+
+        return (this.port);
+
+    }
+
+
+    /**
+     * Return the protocol name part of the URL.
+     */
+    public String getProtocol() {
+
+        return (this.protocol);
+
+    }
+
+
+    /**
+     * Return the query part of the URL.
+     */
+    public String getQuery() {
+
+        return (this.query);
+
+    }
+
+
+    /**
+     * Return the reference part of the URL.
+     */
+    public String getRef() {
+
+        return (this.ref);
+
+    }
+
+
+    /**
+     * Return the user info part of the URL.
+     */
+    public String getUserInfo() {
+
+        return (this.userInfo);
+
+    }
+
+
+    /**
+     * Normalize the <code>path</code> (and therefore <code>file</code>)
+     * portions of this URL.
+     * <p>
+     * <strong>NOTE</strong> - This method is not part of the public API
+     * of <code>java.net.URL</code>, but is provided as a value added
+     * service of this implementation.
+     *
+     * @exception MalformedURLException if a normalization error occurs,
+     *  such as trying to move about the hierarchical root
+     */
+    public void normalize() throws MalformedURLException {
+
+        // Special case for null path
+        if (path == null) {
+            if (query != null)
+                file = "?" + query;
+            else
+                file = "";
+            return;
+        }
+
+        // Create a place for the normalized path
+        String normalized = path;
+        if (normalized.equals("/.")) {
+            path = "/";
+            if (query != null)
+                file = path + "?" + query;
+            else
+                file = path;
+            return;
+        }
+
+        // Normalize the slashes and add leading slash if necessary
+        if (normalized.indexOf('\\') >= 0)
+            normalized = normalized.replace('\\', '/');
+        if (!normalized.startsWith("/"))
+            normalized = "/" + normalized;
+
+        // Resolve occurrences of "//" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("//");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 1);
+        }
+
+        // Resolve occurrences of "/./" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/./");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 2);
+        }
+
+        // Resolve occurrences of "/../" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/../");
+            if (index < 0)
+                break;
+            if (index == 0)
+                throw new MalformedURLException
+                    ("Invalid relative URL reference");
+            int index2 = normalized.lastIndexOf('/', index - 1);
+            normalized = normalized.substring(0, index2) +
+                normalized.substring(index + 3);
+        }
+
+        // Resolve occurrences of "/." at the end of the normalized path
+        if (normalized.endsWith("/."))
+            normalized = normalized.substring(0, normalized.length() - 1);
+
+        // Resolve occurrences of "/.." at the end of the normalized path
+        if (normalized.endsWith("/..")) {
+            int index = normalized.length() - 3;
+            int index2 = normalized.lastIndexOf('/', index - 1);
+            if (index2 < 0)
+                throw new MalformedURLException
+                    ("Invalid relative URL reference");
+            normalized = normalized.substring(0, index2 + 1);
+        }
+
+        // Return the normalized path that we have completed
+        path = normalized;
+        if (query != null)
+            file = path + "?" + query;
+        else
+            file = path;
+
+    }
+
+
+    /**
+     * Compare two URLs, excluding the "ref" fields.  Returns <code>true</code>
+     * if this <code>URL</code> and the <code>other</code> argument both refer
+     * to the same resource.  The two <code>URLs</code> might not both contain
+     * the same anchor.
+     */
+    public boolean sameFile(URL other) {
+
+        if (!compare(protocol, other.getProtocol()))
+            return (false);
+        if (!compare(host, other.getHost()))
+            return (false);
+        if (port != other.getPort())
+            return (false);
+        if (!compare(file, other.getFile()))
+            return (false);
+        return (true);
+
+    }
+
+
+    /**
+     * Return a string representation of this URL.  This follow the rules in
+     * RFC 2396, Section 5.2, Step 7.
+     */
+    public String toExternalForm() {
+
+        StringBuilder sb = new StringBuilder();
+        if (protocol != null) {
+            sb.append(protocol);
+            sb.append(":");
+        }
+        if (authority != null) {
+            sb.append("//");
+            sb.append(authority);
+        }
+        if (path != null)
+            sb.append(path);
+        if (query != null) {
+            sb.append('?');
+            sb.append(query);
+        }
+        if (ref != null) {
+            sb.append('#');
+            sb.append(ref);
+        }
+        return (sb.toString());
+
+    }
+
+
+    /**
+     * Return a string representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("URL[");
+        sb.append("authority=");
+        sb.append(authority);
+        sb.append(", file=");
+        sb.append(file);
+        sb.append(", host=");
+        sb.append(host);
+        sb.append(", port=");
+        sb.append(port);
+        sb.append(", protocol=");
+        sb.append(protocol);
+        sb.append(", query=");
+        sb.append(query);
+        sb.append(", ref=");
+        sb.append(ref);
+        sb.append(", userInfo=");
+        sb.append(userInfo);
+        sb.append("]");
+        return (sb.toString());
+
+        //        return (toExternalForm());
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Compare to String values for equality, taking appropriate care if one
+     * or both of the values are <code>null</code>.
+     *
+     * @param first First string
+     * @param second Second string
+     */
+    private boolean compare(String first, String second) {
+
+        if (first == null) {
+            if (second == null)
+                return (true);
+            else
+                return (false);
+        } else {
+            if (second == null)
+                return (false);
+            else
+                return (first.equals(second));
+        }
+
+    }
+
+
+    /**
+     * Parse the specified portion of the string representation of a URL,
+     * assuming that it has a format similar to that for <code>http</code>.
+     *
+     * <p><strong>FIXME</strong> - This algorithm can undoubtedly be optimized
+     * for performance.  However, that needs to wait until after sufficient
+     * unit tests are implemented to guarantee correct behavior with no
+     * regressions.</p>
+     *
+     * @param spec String representation being parsed
+     * @param start Starting offset, which will be just after the ':' (if
+     *  there is one) that determined the protocol name
+     * @param limit Ending position, which will be the position of the '#'
+     *  (if there is one) that delimited the anchor
+     *
+     * @exception MalformedURLException if a parsing error occurs
+     */
+    private void parse(String spec, int start, int limit)
+        throws MalformedURLException {
+
+        // Trim the query string (if any) off the tail end
+        int question = spec.lastIndexOf('?', limit - 1);
+        if ((question >= 0) && (question < limit)) {
+            query = spec.substring(question + 1, limit);
+            limit = question;
+        } else {
+            query = null;
+        }
+
+        // Parse the authority section
+        if (spec.indexOf("//", start) == start) {
+            int pathStart = spec.indexOf("/", start + 2);
+            if ((pathStart >= 0) && (pathStart < limit)) {
+                authority = spec.substring(start + 2, pathStart);
+                start = pathStart;
+            } else {
+                authority = spec.substring(start + 2, limit);
+                start = limit;
+            }
+            if (authority.length() > 0) {
+                int at = authority.indexOf('@');
+                if( at >= 0 ) {
+                    userInfo = authority.substring(0,at);
+                }
+                int ipv6 = authority.indexOf('[',at+1);
+                int hStart = at+1;
+                if( ipv6 >= 0 ) {
+                    hStart = ipv6;
+                    ipv6 = authority.indexOf(']', ipv6);
+                    if( ipv6 < 0 ) {
+                        throw new MalformedURLException(
+                                                        "Closing ']' not found in IPV6 address: " + authority);
+                    } else {
+                        at = ipv6-1;
+                    }
+                }
+                                                        
+                int colon = authority.indexOf(':', at+1);
+                if (colon >= 0) {
+                    try {
+                        port =
+                            Integer.parseInt(authority.substring(colon + 1));
+                    } catch (NumberFormatException e) {
+                        throw new MalformedURLException(e.toString());
+                    }
+                    host = authority.substring(hStart, colon);
+                } else {
+                    host = authority.substring(hStart);
+                    port = -1;
+                }
+            }
+        }
+
+        // Parse the path section
+        if (spec.indexOf("/", start) == start) {     // Absolute path
+            path = spec.substring(start, limit);
+            if (query != null)
+                file = path + "?" + query;
+            else
+                file = path;
+            return;
+        }
+
+        // Resolve relative path against our context's file
+        if (path == null) {
+            if (query != null)
+                file = "?" + query;
+            else
+                file = null;
+            return;
+        }
+        if (!path.startsWith("/"))
+            throw new MalformedURLException
+                ("Base path does not start with '/'");
+        if (!path.endsWith("/"))
+            path += "/../";
+        path += spec.substring(start, limit);
+        if (query != null)
+            file = path + "?" + query;
+        else
+            file = path;
+        return;
+
+    }
+
+    /**
+     * Determine if the character is allowed in the scheme of a URI.
+     * See RFC 2396, Section 3.1
+     */
+    public static boolean isSchemeChar(char c) {
+        return Character.isLetterOrDigit(c) ||
+            c == '+' || c == '-' || c == '.';
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings.properties
new file mode 100644
index 0000000..5e8ce34
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings.properties
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+jsse.alias_no_key_entry=Alias name {0} does not identify a key entry
+jsse.keystore_load_failed=Failed to load keystore type {0} with path {1} due to {2}
+jsse.invalid_ssl_conf=SSL configuration is invalid due to {0}
+jsse.invalid_truststore_password=The provided trust store password could not be used to unlock and/or validate the trust store. Retrying to access the trust store with a null password which will skip validation.
+jsse.invalidTrustManagerClassName=The trustManagerClassName provided [{0}] does not implement javax.net.ssl.TrustManager 
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_es.properties
new file mode 100644
index 0000000..fa8adec
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_es.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+jsse.alias_no_key_entry = El nombre de Alias {0} no identifica una entrada de clave
+jsse.keystore_load_failed = No pude cargar almac\u00E9n de claves de tipo {0} con ruta {1} debido a {2}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_fr.properties
new file mode 100644
index 0000000..4e73340
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_fr.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+jsse.alias_no_key_entry=Le nom alias {0} n''identifie pas une entr\u00e9e de clef
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_ja.properties
new file mode 100644
index 0000000..adeb951
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/res/LocalStrings_ja.properties
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+jsse.alias_no_key_entry=\u5225\u540d {0} \u306f\u30ad\u30fc\u30a8\u30f3\u30c8\u30ea\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings.properties
new file mode 100644
index 0000000..96bdd7e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings.properties
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# net resources
+endpoint.err.fatal=Endpoint {0} shutdown due to exception: {1}
+endpoint.err.nonfatal=Endpoint {0} ignored exception: {1}
+endpoint.warn.reinit=Reinitializing ServerSocket
+endpoint.warn.restart=Restarting endpoint
+endpoint.warn.security=Endpoint {0} security exception: {1}
+endpoint.err.socket=Socket error caused by remote host {0}
+endpoint.err.handshake=Handshake failed
+endpoint.err.unexpected=Unexpected error processing socket
+endpoint.warn.nullSocket=Null socket returned by accept
+endpoint.debug.unlock=Caught exception trying to unlock accept on port {0}
+endpoint.err.close=Caught exception trying to close socket
+endpoint.noProcessor=No Processors - worker thread dead!
+endpoint.info.maxThreads=Maximum number of threads ({0}) created for connector with address {1} and port {2}
+
+endpoint.init.bind=Socket bind failed: [{0}] {1}
+endpoint.init.listen=Socket listen failed: [{0}] {1}
+endpoint.init.notavail=APR not available
+endpoint.accept.fail=Socket accept failed
+endpoint.launch.fail=Failed to launch new runnable
+endpoint.poll.limitedpollsize=Failed to create poller with specified size of {0}
+endpoint.poll.initfail=Poller creation failed
+endpoint.poll.fail=Critical poller failure (restarting poller): [{0}] {1}
+endpoint.poll.error=Unexpected poller error
+endpoint.process.fail=Error allocating socket processor
+endpoint.sendfile.error=Unexpected sendfile error
+endpoint.sendfile.addfail=Sendfile failure: [{0}] {1}
+endpoint.sendfile.nosupport=Disabling sendfile, since either the APR version or the system doesn't support it
+endpoint.warn.noInsecureReneg=Secure renegotation is not supported by the SSL library {0}
+endpoint.warn.unlockAcceptorFailed=Acceptor thread [{0}] failed to unlock. Forcing hard socket shutdown.
+endpoint.debug.channelCloseFail=Failed to close channel
+endpoint.debug.socketCloseFail=Failed to close socket
+endpoint.apr.noSslCertFile=Connector attribute SSLCertificateFile must be defined when using SSL with APR
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_es.properties
new file mode 100644
index 0000000..2d44701
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_es.properties
@@ -0,0 +1,39 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# net resources
+endpoint.err.fatal = Punto Final (Endpoint) {0} parado debido a excepci\u00F3n\: {1}
+endpoint.err.nonfatal = El Punto Final (Endpoint) {0} ignor\u00F3 excepci\u00F3n\: {1}
+endpoint.warn.reinit = Reinicializando ServerSocket
+endpoint.warn.restart = Rearrancando punto final (endpoint)
+endpoint.warn.security = Punto Final (Endpoint) {0} con excepci\u00F3n de seguridad\: {1}
+endpoint.err.socket = Error de conector causado por m\u00E1quina remota {0}
+endpoint.err.handshake = Acuerdo fallido
+endpoint.err.unexpected = Error inesperado al procesar conector
+endpoint.warn.nullSocket = Conector nulo devuelto por aceptaci\u00F3n
+endpoint.debug.unlock = Excepci\u00F3n cogida intentando desbloquear aceptaci\u00F3n en puerto {0}
+endpoint.err.close = Excepci\u00F3n cogida intentando cerrar conector
+endpoint.noProcessor = No hay procesadores - \u00A1hilo de trabajadir muerto\!
+endpoint.init.bind = Ligado de conector fall\u00F3\: [{0}] {1}
+endpoint.init.listen = Escucha de conector fall\u00F3\: [{0}] {1}
+endpoint.init.notavail = APR no disponible
+endpoint.accept.fail = Aceptaci\u00F3n de conector fall\u00F3
+endpoint.poll.limitedpollsize = No pude crear encuestador de medida espec\u00EDfica de {0}
+endpoint.poll.initfail = Fall\u00F3 la creaci\u00F3n del encuestador
+endpoint.poll.fail = Fallo cr\u00EDtico de encuestador (reiniciando encuestador)\: [{0}] {1}
+endpoint.poll.error = Error inesperado de encuestador
+endpoint.process.fail = Error reservando procesador de conector
+endpoint.sendfile.error = Error inesperado de env\u00EDo de fichero
+endpoint.sendfile.addfail = Fallo en Sednfile\: [{0}] {1}
+endpoint.sendfile.nosupport = Desactivando sendfile ya que o la versi\u00F3n APR o el sistema no lo soportan
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_fr.properties
new file mode 100644
index 0000000..849f829
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_fr.properties
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# net resources
+endpoint.err.fatal=Le point de contact {0} a \u00e9t\u00e9 \u00e9teint suite \u00e0 l''exception{1}
+endpoint.err.nonfatal=Le point de contact {0} a ignor\u00e9 l''exception: {1}
+endpoint.warn.reinit=R\u00e9initialisation du ServerSocket
+endpoint.warn.restart=Red\u00e9marrage du point de contact
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_ja.properties
new file mode 100644
index 0000000..22101a9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/res/LocalStrings_ja.properties
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# net resources
+endpoint.err.fatal=\u4f8b\u5916\u306e\u305f\u3081\u306b\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8 {0} \u306f\u30b7\u30e3\u30c3\u30c8\u30c0\u30a6\u30f3\u3057\u307e\u3059: {1}
+endpoint.err.nonfatal=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8 {0} \u306f\u4f8b\u5916\u3092\u7121\u8996\u3057\u307e\u3057\u305f: {1}
+endpoint.warn.reinit=ServerSocket\u3092\u518d\u521d\u671f\u5316\u3057\u307e\u3059
+endpoint.warn.restart=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u3092\u518d\u8d77\u52d5\u3057\u307e\u3059
+endpoint.warn.security=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8 {0} \u306e\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u4f8b\u5916\u3067\u3059: {1}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/res/StringManager.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/res/StringManager.java
new file mode 100644
index 0000000..c8cf41a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/res/StringManager.java
@@ -0,0 +1,206 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.res;
+
+import java.text.MessageFormat;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * An internationalization / localization helper class which reduces
+ * the bother of handling ResourceBundles and takes care of the
+ * common cases of message formating which otherwise require the
+ * creation of Object arrays and such.
+ *
+ * <p>The StringManager operates on a package basis. One StringManager
+ * per package can be created and accessed via the getManager method
+ * call.
+ *
+ * <p>The StringManager will look for a ResourceBundle named by
+ * the package name given plus the suffix of "LocalStrings". In
+ * practice, this means that the localized information will be contained
+ * in a LocalStrings.properties file located in the package
+ * directory of the classpath.
+ *
+ * <p>Please see the documentation for java.util.ResourceBundle for
+ * more information.
+ *
+ * @version $Id: StringManager.java,v 1.1 2011/06/28 21:14:19 rherrmann Exp $
+ *
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Mel Martinez [mmartinez@g1440.com]
+ * @see java.util.ResourceBundle
+ */
+
+public class StringManager {
+
+    /**
+     * The ResourceBundle for this StringManager.
+     */
+    private final ResourceBundle bundle;
+    private final Locale locale;
+
+    /**
+     * Creates a new StringManager for a given package. This is a
+     * private method and all access to it is arbitrated by the
+     * static getManager method call so that only one StringManager
+     * per package will be created.
+     *
+     * @param packageName Name of package to create StringManager for.
+     */
+    private StringManager(String packageName, Locale locale) {
+        String bundleName = packageName + ".LocalStrings";
+        ResourceBundle bnd = null;
+        try {
+            bnd = ResourceBundle.getBundle(bundleName, locale);
+        } catch( MissingResourceException ex ) {
+            // Try from the current loader (that's the case for trusted apps)
+            // Should only be required if using a TC5 style classloader structure
+            // where common != shared != server
+            ClassLoader cl = Thread.currentThread().getContextClassLoader();
+            if( cl != null ) {
+                try {
+                    bnd = ResourceBundle.getBundle(bundleName, locale, cl);
+                } catch(MissingResourceException ex2) {
+                    // Ignore
+                }
+            }
+        }
+        bundle = bnd;
+        // Get the actual locale, which may be different from the requested one
+        if (bundle != null) {
+            this.locale = bundle.getLocale();
+        } else {
+            this.locale = null;
+        }
+    }
+
+    /**
+        Get a string from the underlying resource bundle or return
+        null if the String is not found.
+     
+        @param key to desired resource String
+        @return resource String matching <i>key</i> from underlying
+                bundle or null if not found.
+        @throws IllegalArgumentException if <i>key</i> is null.        
+     */
+    public String getString(String key) {
+        if(key == null){
+            String msg = "key may not have a null value";
+
+            throw new IllegalArgumentException(msg);
+        }
+
+        String str = null;
+
+        try {
+            // Avoid NPE if bundle is null and treat it like an MRE
+            if (bundle != null) {
+                str = bundle.getString(key);
+            }
+        } catch(MissingResourceException mre) {
+            //bad: shouldn't mask an exception the following way:
+            //   str = "[cannot find message associated with key '" + key +
+            //         "' due to " + mre + "]";
+            //     because it hides the fact that the String was missing
+            //     from the calling code.
+            //good: could just throw the exception (or wrap it in another)
+            //      but that would probably cause much havoc on existing
+            //      code.
+            //better: consistent with container pattern to
+            //      simply return null.  Calling code can then do
+            //      a null check.
+            str = null;
+        }
+
+        return str;
+    }
+
+    /**
+     * Get a string from the underlying resource bundle and format
+     * it with the given set of arguments.
+     *
+     * @param key
+     * @param args
+     */
+    public String getString(final String key, final Object... args) {
+        String value = getString(key);
+        if (value == null) {
+            value = key;
+        }
+
+        MessageFormat mf = new MessageFormat(value);
+        mf.setLocale(locale);
+        return mf.format(args, new StringBuffer(), null).toString();
+    }
+
+    /**
+     * Identify the Locale this StringManager is associated with
+     */
+    public Locale getLocale() {
+        return locale;
+    }
+
+    // --------------------------------------------------------------
+    // STATIC SUPPORT METHODS
+    // --------------------------------------------------------------
+
+    private static final Map<String, Map<Locale,StringManager>> managers =
+        new Hashtable<String, Map<Locale,StringManager>>();
+
+    /**
+     * Get the StringManager for a particular package. If a manager for
+     * a package already exists, it will be reused, else a new
+     * StringManager will be created and returned.
+     *
+     * @param packageName The package name
+     */
+    public static final synchronized StringManager getManager(
+            String packageName) {
+        return getManager(packageName, Locale.getDefault());
+    }
+
+    /**
+     * Get the StringManager for a particular package and Locale. If a manager
+     * for a package/Locale combination already exists, it will be reused, else
+     * a new StringManager will be created and returned.
+     *
+     * @param packageName The package name
+     * @param locale      The Locale
+     */
+    public static final synchronized StringManager getManager(
+            String packageName, Locale locale) {
+
+        Map<Locale,StringManager> map = managers.get(packageName);
+        if (map == null) {
+            map = new Hashtable<Locale, StringManager>();
+            managers.put(packageName, map);
+        }
+        
+        StringManager mgr = map.get(locale);
+        if (mgr == null) {
+            mgr = new StringManager(packageName, locale);
+            map.put(locale, mgr);
+        }
+        return mgr;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/Constants.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/Constants.java
new file mode 100644
index 0000000..9578ac2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+/**
+ * Static constants for this package.
+ */
+public final class Constants {
+
+    public static final String Package = "org.apache.tomcat.util.threads";
+
+    public static final long DEFAULT_THREAD_RENEWAL_DELAY = 1000L;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/CounterLatch.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/CounterLatch.java
new file mode 100644
index 0000000..2b3718c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/CounterLatch.java
@@ -0,0 +1,179 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
+/**
+ * Simple counter latch that allows code to keep an up and down counter, and waits while the latch holds a certain wait value.
+ * and threads using the latch to wait if the count has reached a certain value.
+ * The counter latch can be used to keep track of an atomic counter, since the operations {@link #countDown()}
+ * and {@link #countUp()} are atomic.
+ * When the latch reaches the wait value, threads will block. The counter latch can hence act like a 
+ * count down latch or a count up latch, while letting you keep track of the counter as well.
+ * This counter latch works opposite as the java.util.concurrent.CountDownLatch, since the CounterLatch only blocks on a single value and releases the threads on all other values.
+ * @author fhanik
+ * @see <a href="http://download.oracle.com/javase/6/docs/api/java/util/concurrent/CountDownLatch.html">CountDownLatch</a>
+ *
+ */
+public class CounterLatch {
+
+    private class Sync extends AbstractQueuedSynchronizer {
+        private static final long serialVersionUID = 1L;
+
+        public Sync() {
+        }
+
+        @Override
+        protected int tryAcquireShared(int arg) {
+            return ((!released) && count.get() == signal) ? -1 : 1;
+        }
+
+        @Override
+        protected boolean tryReleaseShared(int arg) {
+            return true;
+        }
+    }
+
+    private final Sync sync;
+    private final AtomicLong count;
+    private volatile long signal;
+    private volatile boolean released = false;
+    
+    /**
+     * Instantiates a CounterLatch object with an initial value and a wait value.
+     * @param initial - initial value of the counter
+     * @param waitValue - when the counter holds this value, 
+     * threads calling {@link #await()} or {@link #await(long, TimeUnit)} 
+     * will wait until the counter changes value or until they are interrupted.  
+     */
+    public CounterLatch(long initial, long waitValue) {
+        this.signal = waitValue;
+        this.count = new AtomicLong(initial);
+        this.sync = new Sync();
+    }
+
+    /**
+     * Causes the calling thread to wait if the counter holds the waitValue.
+     * If the counter holds any other value, the thread will return
+     * If the thread is interrupted or becomes interrupted an InterruptedException is thrown
+     * @throws InterruptedException
+     */
+    public void await() throws InterruptedException {
+        sync.acquireSharedInterruptibly(1);
+    }
+
+    /**
+     * Causes the calling thread to wait if the counter holds the waitValue.
+     * If the counter holds any other value, the thread will return
+     * If the thread is interrupted or becomes interrupted an InterruptedException is thrown
+     * @return true if the value changed, false if the timeout has elapsed
+     * @throws InterruptedException
+     */
+    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
+        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
+    }
+
+    /**
+     * Increments the counter
+     * @return the previous counter value
+     */
+    public long countUp() {
+        long previous = count.getAndIncrement();
+        if (previous == signal) {
+            sync.releaseShared(0);
+        }
+        return previous;
+    }
+
+    /**
+     * Decrements the counter
+     * @return the previous counter value
+     */
+    public long countDown() {
+        long previous = count.getAndDecrement();
+        if (previous == signal) {
+            sync.releaseShared(0);
+        }
+        return previous;
+    }
+    
+    /**
+     * Returns the current counter value
+     * @return the current counter value
+     */
+    public long getCount() {
+        return count.get();
+    }
+    
+    /**
+     * Performs an atomic update of the counter 
+     * If the operation is successful and {@code expect==waitValue && expect!=update} waiting threads will be released.  
+     * @param expect - the expected counter value
+     * @param update - the new counter value
+     * @return <code>true</code> if successful, <code>false</code> if the
+     *         current value wasn't as expected
+     */
+    public boolean compareAndSet(long expect, long update) {
+        boolean result = count.compareAndSet(expect, update);
+        if (result && expect==signal && expect != update) {
+            sync.releaseShared(0);
+        }
+        return result;
+    }
+    
+    /**
+     * returns true if there are threads blocked by this latch
+     * @return true if there are threads blocked by this latch
+     */
+    public boolean hasQueuedThreads() {
+        return sync.hasQueuedThreads();
+    }
+    
+    /**
+     * Returns a collection of the blocked threads
+     * @return a collection of the blocked threads
+     */
+    public Collection<Thread> getQueuedThreads() {
+        return sync.getQueuedThreads();
+    }
+    
+    /**
+     * releases all waiting threads. This operation is permanent, and no threads will block,
+     * even if the counter hits the {@code waitValue} until {@link #reset(long)} has been called.
+     * @return <code>true</code> if this release of shared mode may permit a
+     *         waiting acquire (shared or exclusive) to succeed; and
+     *         <code>false</code> otherwise
+     */
+    public boolean releaseAll() {
+        released = true;
+        return sync.releaseShared(0);
+    }
+    
+    /**
+     * Resets the latch and initializes the counter with the new value.
+     * @param value the new counter value
+     * @see #releaseAll()
+     */
+    public void reset(long value) {
+        this.count.set(value);
+        released = false;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/DedicatedThreadExecutor.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/DedicatedThreadExecutor.java
new file mode 100644
index 0000000..43e4411
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/DedicatedThreadExecutor.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * A utility class to execute a {@link Callable} in a dedicated thread.
+ * It can be used either with an instance to reuse the same thread for each call
+ * to {@link #execute(Callable)} or with the static method
+ * {@link #executeInOwnThread(Callable)}. When using an instance,
+ * {@link #shutdown()} must be called when the instance is no longer needed to
+ * dispose of the dedicated thread.
+ */
+public class DedicatedThreadExecutor {
+    private final SingleThreadFactory singleThreadFactory =
+        new SingleThreadFactory();
+    private final ExecutorService executorService =
+        Executors.newSingleThreadExecutor(singleThreadFactory);
+
+    /**
+     * Executes the given {@link Callable} with the thread spawned for the
+     * current {@link DedicatedThreadExecutor} instance, and returns its result.
+     * 
+     * @param <V>
+     *            the type of the returned value
+     * @param callable
+     * @return the completed result
+     */
+    public <V> V execute(final Callable<V> callable) {
+        final Future<V> futureTask = executorService.submit(callable);
+
+        boolean interrupted = false;
+        V result;
+        while (true) {
+            try {
+                result = futureTask.get();
+                break;
+            } catch (InterruptedException e) {
+                // keep waiting
+                interrupted = true;
+            } catch (ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        if (interrupted) {
+            // set interruption status so that the caller may react to it
+            Thread.currentThread().interrupt();
+        }
+        return result;
+    }
+
+    /**
+     * Stops the dedicated thread and waits for its death.
+     */
+    public void shutdown() {
+        executorService.shutdown();
+        if (singleThreadFactory.singleThread != null) {
+            boolean interrupted = false;
+            while (true) {
+                try {
+                    singleThreadFactory.singleThread.join();
+                    singleThreadFactory.singleThread = null;
+                    break;
+                } catch (InterruptedException e) {
+                    // keep waiting
+                    interrupted = true;
+                }
+            }
+            if (interrupted) {
+                // set interruption status so that the caller may react to it
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Executes the given {@link Callable} in a new thread and returns the
+     * result after the thread is stopped.
+     * 
+     * @param <V>
+     * @param callable
+     * @return the completed result
+     */
+    public static <V> V executeInOwnThread(
+        final Callable<V> callable) {
+        DedicatedThreadExecutor executor = new DedicatedThreadExecutor();
+        try {
+            return executor.execute(callable);
+        } finally {
+            executor.shutdown();
+        }
+
+    }
+
+    // we use a ThreadFactory so that we can later call Thread.join().
+    // Indeed, calling shutdown() on an ExecutorService will eventually stop the
+    // thread but it might still be alive when execute() returns (race
+    // condition).
+    // This can lead to false alarms about potential memory leaks because the
+    // thread may have a web application class loader for its context class
+    // loader.
+    private static class SingleThreadFactory implements ThreadFactory {
+        private volatile Thread singleThread;
+
+        @Override
+        public Thread newThread(Runnable r) {
+            if (singleThread != null) {
+                throw new IllegalStateException(
+                    "should not have been called more than once");
+            }
+            singleThread = new Thread(r);
+            singleThread.setDaemon(true);
+            return singleThread;
+        }
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/ResizableExecutor.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/ResizableExecutor.java
new file mode 100644
index 0000000..0e8f0d2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/ResizableExecutor.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+import java.util.concurrent.Executor;
+
+public interface ResizableExecutor extends Executor {
+
+    /**
+     * Returns the current number of threads in the pool.
+     *
+     * @return the number of threads
+     */
+    public int getPoolSize();
+    
+    public int getMaxThreads();
+
+    /**
+     * Returns the approximate number of threads that are actively executing
+     * tasks.
+     *
+     * @return the number of threads
+     */
+    public int getActiveCount();
+    
+    public boolean resizePool(int corePoolSize, int maximumPoolSize);
+    
+    public boolean resizeQueue(int capacity);
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskQueue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskQueue.java
new file mode 100644
index 0000000..eb606f4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskQueue.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+import java.util.Collection;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * As task queue specifically designed to run with a thread pool executor.
+ * The task queue is optimised to properly utilize threads within 
+ * a thread pool executor. If you use a normal queue, the executor will spawn threads
+ * when there are idle threads and you wont be able to force items unto the queue itself 
+ * @author fhanik
+ *
+ */
+public class TaskQueue extends LinkedBlockingQueue<Runnable> {
+
+    private static final long serialVersionUID = 1L;
+
+    private ThreadPoolExecutor parent = null;
+    
+    // no need to be volatile, the one times when we change and read it occur in
+    // a single thread (the one that did stop a context and fired listeners)
+    private Integer forcedRemainingCapacity = null;
+
+    public TaskQueue() {
+        super();
+    }
+
+    public TaskQueue(int capacity) {
+        super(capacity);
+    }
+
+    public TaskQueue(Collection<? extends Runnable> c) {
+        super(c);
+    }
+
+    public void setParent(ThreadPoolExecutor tp) {
+        parent = tp;
+    }
+    
+    public boolean force(Runnable o) {
+        if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
+        return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
+    }
+
+    public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
+        if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
+        return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected
+    }
+
+    @Override
+    public boolean offer(Runnable o) {
+      //we can't do any checks
+        if (parent==null) return super.offer(o);
+        //we are maxed out on threads, simply queue the object
+        if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
+        //we have idle threads, just add it to the queue
+        if (parent.getSubmittedCount()<(parent.getPoolSize())) return super.offer(o);
+        //if we have less threads than maximum force creation of a new thread
+        if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
+        //if we reached here, we need to add it to the queue
+        return super.offer(o);
+    }
+
+
+    @Override
+    public Runnable poll(long timeout, TimeUnit unit)
+            throws InterruptedException {
+        Runnable runnable = super.poll(timeout, unit);
+        if (runnable == null && parent != null) {
+            // the poll timed out, it gives an opportunity to stop the current
+            // thread if needed to avoid memory leaks.
+            parent.stopCurrentThreadIfNeeded();
+        }
+        return runnable;
+    }
+
+    @Override
+    public Runnable take() throws InterruptedException {
+        if (parent != null && parent.currentThreadShouldBeStopped()) {
+            return poll(parent.getKeepAliveTime(TimeUnit.MILLISECONDS),
+                    TimeUnit.MILLISECONDS);
+            // yes, this may return null (in case of timeout) which normally
+            // does not occur with take()
+            // but the ThreadPoolExecutor implementation allows this
+        }
+        return super.take();
+    }
+
+    @Override
+    public int remainingCapacity() {
+        if (forcedRemainingCapacity != null) {
+            // ThreadPoolExecutor.setCorePoolSize checks that
+            // remainingCapacity==0 to allow to interrupt idle threads
+            // I don't see why, but this hack allows to conform to this
+            // "requirement"
+            return forcedRemainingCapacity.intValue();
+        }
+        return super.remainingCapacity();
+    }
+
+    public void setForcedRemainingCapacity(Integer forcedRemainingCapacity) {
+        this.forcedRemainingCapacity = forcedRemainingCapacity;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskThread.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskThread.java
new file mode 100644
index 0000000..6038d9d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskThread.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+/**
+ * A Thread implementation that records the time at which it was created.
+ * 
+ */
+public class TaskThread extends Thread {
+
+    private final long creationTime;
+
+    public TaskThread(ThreadGroup group, Runnable target, String name) {
+        super(group, target, name);
+        this.creationTime = System.currentTimeMillis();
+    }
+
+    public TaskThread(ThreadGroup group, Runnable target, String name,
+            long stackSize) {
+        super(group, target, name, stackSize);
+        this.creationTime = System.currentTimeMillis();
+    }
+
+    /**
+     * @return the time (in ms) at which this thread was created
+     */
+    public final long getCreationTime() {
+        return creationTime;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskThreadFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskThreadFactory.java
new file mode 100644
index 0000000..59b87ee
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/TaskThreadFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+/**
+ * Simple task thread factory to use to create threads for an executor implementation.
+ * @author fhanik
+ *
+ */
+public class TaskThreadFactory implements ThreadFactory {
+    private final ThreadGroup group;
+    private final AtomicInteger threadNumber = new AtomicInteger(1);
+    private final String namePrefix;
+    private final boolean daemon;
+    private final int threadPriority;
+    public TaskThreadFactory(String namePrefix, boolean daemon, int priority) {
+        SecurityManager s = System.getSecurityManager();
+        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+        this.namePrefix = namePrefix;
+        this.daemon = daemon;
+        this.threadPriority = priority;
+    }
+
+    @Override
+    public Thread newThread(Runnable r) {
+        TaskThread t = new TaskThread(group, r, namePrefix + threadNumber.getAndIncrement());
+        t.setDaemon(daemon);
+        t.setPriority(threadPriority);
+        return t;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/ThreadPoolExecutor.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/ThreadPoolExecutor.java
new file mode 100644
index 0000000..67f7b0c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/ThreadPoolExecutor.java
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.threads;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Same as a java.util.concurrent.ThreadPoolExecutor but implements a much more efficient
+ * {@link #getSubmittedCount()} method, to be used to properly handle the work queue.
+ * If a RejectedExecutionHandler is not specified a default one will be configured
+ * and that one will always throw a RejectedExecutionException
+ * @author fhanik
+ *
+ */
+public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager
+            .getManager("org.apache.tomcat.util.threads.res");
+
+    private static final Log log = LogFactory.getLog(ThreadPoolExecutor.class);
+
+    /**
+     * The number of tasks submitted but not yet finished. This includes tasks
+     * in the queue and tasks that have been handed to a worker thread but the
+     * latter did not start executing the task yet.
+     * This number is always greater or equal to {@link #getActiveCount()}.
+     */
+    private final AtomicInteger submittedCount = new AtomicInteger(0);
+    private final AtomicLong lastContextStoppedTime = new AtomicLong(0L);
+
+    /**
+     * Most recent time in ms when a thread decided to kill itself to avoid
+     * potential memory leaks. Useful to throttle the rate of renewals of
+     * threads.
+     */
+    private final AtomicLong lastTimeThreadKilledItself = new AtomicLong(0L);
+
+    /**
+     * Delay in ms between 2 threads being renewed. If negative, do not renew threads.
+     */
+    private long threadRenewalDelay = Constants.DEFAULT_THREAD_RENEWAL_DELAY;
+
+    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
+    }
+
+    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
+            RejectedExecutionHandler handler) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+    }
+
+    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new RejectHandler());
+    }
+
+    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new RejectHandler());
+    }
+    
+    public long getThreadRenewalDelay() {
+        return threadRenewalDelay;
+    }
+
+    public void setThreadRenewalDelay(long threadRenewalDelay) {
+        this.threadRenewalDelay = threadRenewalDelay;
+    }
+
+    @Override
+    protected void afterExecute(Runnable r, Throwable t) {
+        submittedCount.decrementAndGet();
+
+        if (t == null) {
+            stopCurrentThreadIfNeeded();
+        }
+    }
+
+    /**
+     * If the current thread was started before the last time when a context was
+     * stopped, an exception is thrown so that the current thread is stopped.
+     */
+    protected void stopCurrentThreadIfNeeded() {
+        if (currentThreadShouldBeStopped()) {
+            long lastTime = lastTimeThreadKilledItself.longValue();
+            if (lastTime + threadRenewalDelay < System.currentTimeMillis()) {
+                if (lastTimeThreadKilledItself.compareAndSet(lastTime,
+                        System.currentTimeMillis() + 1)) {
+                    // OK, it's really time to dispose of this thread
+
+                    final String msg = sm.getString(
+                                    "threadPoolExecutor.threadStoppedToAvoidPotentialLeak",
+                                    Thread.currentThread().getName());
+
+                    Thread.currentThread().setUncaughtExceptionHandler(
+                            new UncaughtExceptionHandler() {
+                                @Override
+                                public void uncaughtException(Thread t,
+                                        Throwable e) {
+                                    // yes, swallow the exception
+                                    log.debug(msg);
+                                }
+                            });
+                    throw new RuntimeException(msg);
+                }
+            }
+        }
+    }
+    
+    protected boolean currentThreadShouldBeStopped() {
+        if (threadRenewalDelay >= 0
+            && Thread.currentThread() instanceof TaskThread) {
+            TaskThread currentTaskThread = (TaskThread) Thread.currentThread();
+            if (currentTaskThread.getCreationTime() < 
+                    this.lastContextStoppedTime.longValue()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public int getSubmittedCount() {
+        return submittedCount.get();
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void execute(Runnable command) {
+        execute(command,0,TimeUnit.MILLISECONDS);
+    }
+    
+    /**
+     * Executes the given command at some time in the future.  The command
+     * may execute in a new thread, in a pooled thread, or in the calling
+     * thread, at the discretion of the <tt>Executor</tt> implementation.
+     * If no threads are available, it will be added to the work queue.
+     * If the work queue is full, the system will wait for the specified 
+     * time and it throw a RejectedExecutionException if the queue is still 
+     * full after that.
+     *
+     * @param command the runnable task
+     * @throws RejectedExecutionException if this task cannot be
+     * accepted for execution - the queue is full
+     * @throws NullPointerException if command or unit is null
+     */
+    public void execute(Runnable command, long timeout, TimeUnit unit) {
+        submittedCount.incrementAndGet();
+        try {
+            super.execute(command);
+        } catch (RejectedExecutionException rx) {
+            if (super.getQueue() instanceof TaskQueue) {
+                final TaskQueue queue = (TaskQueue)super.getQueue();
+                try {
+                    if (!queue.force(command, timeout, unit)) {
+                        submittedCount.decrementAndGet();
+                        throw new RejectedExecutionException("Queue capacity is full.");
+                    }
+                } catch (InterruptedException x) {
+                    submittedCount.decrementAndGet();
+                    Thread.interrupted();
+                    throw new RejectedExecutionException(x);
+                }
+            } else {
+                submittedCount.decrementAndGet();
+                throw rx;
+            }
+            
+        }
+    }
+
+    public void contextStopping() {
+        this.lastContextStoppedTime.set(System.currentTimeMillis());
+
+        // save the current pool parameters to restore them later
+        int savedCorePoolSize = this.getCorePoolSize();
+        TaskQueue taskQueue =
+                getQueue() instanceof TaskQueue ? (TaskQueue) getQueue() : null;
+        if (taskQueue != null) {
+            // note by slaurent : quite oddly threadPoolExecutor.setCorePoolSize
+            // checks that queue.remainingCapacity()==0. I did not understand
+            // why, but to get the intended effect of waking up idle threads, I
+            // temporarily fake this condition.
+            taskQueue.setForcedRemainingCapacity(Integer.valueOf(0));
+        }
+
+        // setCorePoolSize(0) wakes idle threads
+        this.setCorePoolSize(0);
+
+        // wait a little so that idle threads wake and poll the queue again,
+        // this time always with a timeout (queue.poll() instead of
+        // queue.take())
+        // even if we did not wait enough, TaskQueue.take() takes care of timing
+        // out, so that we are sure that all threads of the pool are renewed in
+        // a limited time, something like 
+        // (threadKeepAlive + longest request time)
+        try {
+            Thread.sleep(200L);
+        } catch (InterruptedException e) {
+            // yes, ignore
+        }
+
+        if (taskQueue != null) {
+            // ok, restore the state of the queue and pool
+            taskQueue.setForcedRemainingCapacity(null);
+        }
+        this.setCorePoolSize(savedCorePoolSize);
+    }
+    
+    private static class RejectHandler implements RejectedExecutionHandler {
+        @Override
+        public void rejectedExecution(Runnable r,
+                java.util.concurrent.ThreadPoolExecutor executor) {
+            throw new RejectedExecutionException();
+        }
+        
+    }
+
+
+}